English 中文(简体)
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 command:

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.





相关问题
Undefined reference

I m getting this linker error. I know a way around it, but it s bugging me because another part of the project s linking fine and it s designed almost identically. First, I have namespace LCD. Then I ...

C++ Equivalent of Tidy

Is there an equivalent to tidy for HTML code for C++? I have searched on the internet, but I find nothing but C++ wrappers for tidy, etc... I think the keyword tidy is what has me hung up. I am ...

Template Classes in C++ ... a required skill set?

I m new to C++ and am wondering how much time I should invest in learning how to implement template classes. Are they widely used in industry, or is this something I should move through quickly?

Print possible strings created from a Number

Given a 10 digit Telephone Number, we have to print all possible strings created from that. The mapping of the numbers is the one as exactly on a phone s keypad. i.e. for 1,0-> No Letter for 2->...

typedef ing STL wstring

Why is it when i do the following i get errors when relating to with wchar_t? namespace Foo { typedef std::wstring String; } Now i declare all my strings as Foo::String through out the program, ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

Window iconification status via Xlib

Is it possible to check with the means of pure X11/Xlib only whether the given window is iconified/minimized, and, if it is, how?

热门标签