开发者

C++ linker - Lack of duplicate symbols

Why does the following code not give me a duplicate symbol linker error for Impl?

I ran across this problem in some code I inherited and I'm recreating a shorter version here for simplicity.

I have two classes, Foo and Bar, that each define a different version of the same struct (Impl) in each of their .cpp files. So Foo.cpp and Bar.cpp each have an identically named Impl definition, but each one has a different inline constructor implementation.

Both Foo and Bar have a member variable of type Impl and each forward declares Impl in its .h file.

Foo.cpp news an instance of Bar inside its constructor. What's interesting is what gets created depends on the order the files are linked.

So this compilation command:

g++ -o a.out main.cpp Bar.cpp Foo.cpp

results in this output:

==> main()
Bar.cpp's Impl::Impl()
Bar.cpp's Impl::Impl()
<== main()

And this com开发者_StackOverflow社区mand:

g++ -o a.out main.cpp Foo.cpp Bar.cpp

results in this output:

==> main()
Foo.cpp's Impl::Impl()
Foo.cpp's Impl::Impl()
<== main()

I have tried this with gcc 4.1.2, Visual Studio 2008 and the Green Hills Multi 4.2.4 and they all produce the same result.


Foo.h

#ifndef FOO_H

struct Impl;
class Bar;

class Foo
{
public:
   Foo();
   ~Foo();

private:
   Impl* p;
   Bar* bar;
};

#endif

Foo.cpp

#include <iostream>
#include "Foo.h"
#include "Bar.h"

struct Impl
{
   Impl()
   {
      std::cout << "Foo.cpp's Impl::Impl()" << std::endl;
   }
};

Foo::Foo()
 : p(new Impl),
   bar(new Bar)
{
}

Foo::~Foo()
{
   delete p;
   delete bar;
}

Bar.h

#ifndef BAR_H
#define BAR_H

struct Impl;

class Bar
{
public:
   Bar();
   ~Bar();

private:
   Impl* p;
};

#endif

Bar.cpp

#include <iostream>
#include "Bar.h"

struct Impl
{
   Impl()
   {
      std::cout << "Bar.cpp's Impl::Impl()" << std::endl;
   }
};

Bar::Bar()
 : p(new Impl)
{
}

Bar::~Bar()
{
   delete p;
}

main.cpp

#include <iostream>
#include "Foo.h"

int main (int argc, char const *argv[])
{
   std::cout << "==> main()" << std::endl;
   Foo* f = new Foo();
   std::cout << "<== main()" << std::endl;
   return 0;
}


You're violating the one definition rule, and the compiler/linker isn't required to tell you about it.


G'day,

Isn't default link editor behaviour to take the first symbol that satisfies the requierments and stop searching.

You should be able to enable a complete search to disallow duplicate symbols within the closure of the executable.

Edit: I've just seen that the link editor on Solaris disallows multiple definitions be default. You actually have to use the link editor switch "-z muldefs" to allow linking to proceed with multiple definitions within the objects being used to establish closure for the executable.

Edit2: I'm intrigued here as this should be flagged as a warning. What happens if you add

-std=c++98 -pedantic-errors

to the command line when you build your executable?


Other have already speak about the One Definition Rule, I thought I would chime in some explanation and a real workaround.

Explanation:

I won't explain the One Definition Rule, but I will explain why the linker does not complain. When you use templates, each object get it's own std::vector<int> instantiation. The linker just pick up the first available.

If it was not the case, you would have to explicitly instantiate the template in one source file, then use the extern keyword in the others... but only Comeau supports it.

Work around:

Since I basically suppose you are trying to implement a Pointer to Implementation, you don't have much choice apart from forwarding.

Having dealt with similar problems before (how I hate to have to rely on Pimpl to simplify the dependencies...), I simply rely on a naming convention, coupled to a namespace I reuse for implementation details:

namespace detail { class FooImpl; }

class Foo
{
  typedef detail::FooImpl Impl; // note that the typedef is private
  Impl* m_impl;
};

Simple and efficient. I always use detail for implementation details and I simply append Impl to the end of the class name of which it is supposed to be an Impl.

Notes:

  • A pity you cannot just forward declare it in the class, but there is nothing we can do about it.
  • The namespace details prevents polluting the main namespace where you class lives with those symbols, especially handy for autocompletion by IDE since otherwise you'd get both Foo and FooImpl as propositions.
  • ImplFoo is not great either for autocompletion since all pimpl would begin by Impl!

Hopes it helps.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜