C ++链接器 - 缺少重复的符号

时间:2009-11-16 17:56:54

标签: c++ visual-studio gcc linker

为什么以下代码没有为Impl提供重复的符号链接器错误?

我在我继承的一些代码中遇到了这个问题,为了简单起见,我在这里重新创建了一个较短的版本。

我有两个类,Foo和Bar,它们在每个.cpp文件中定义了相同结构(Impl)的不同版本。所以Foo.cpp和Bar.cpp都有一个同名的Impl定义,但是每个定义都有不同的内联构造函数实现。

Foo和Bar都有一个类型为Impl的成员变量,每个forward都在其.h文件中声明了Impl。

Foo.cpp在其构造函数中新闻一个Bar的实例。有趣的是,创建的内容取决于文件链接的顺序。

所以这个编译命令:

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

导致此输出:

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

这个命令:

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

导致此输出:

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

我已经尝试使用gcc 4.1.2,Visual Studio 2008和Green Hills Multi 4.2.4,它们都会产生相同的结果。


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;
}

3 个答案:

答案 0 :(得分:4)

你违反了one definition rule,编译器/链接器不需要告诉你。

答案 1 :(得分:3)

天儿真好,

默认链接编辑器行为不是采用满足要求的第一个符号并停止搜索。

您应该能够启用完整搜索以禁止可执行文件的闭包内的重复符号。

编辑:我刚刚看到Solaris上的链接编辑器不允许默认使用多个定义。实际上,您必须使用链接编辑器开关“-z muldefs”来允许链接在用于为可执行文件建立闭包的对象中继续进行多个定义。

Edit2:我很感兴趣,因为这应该被标记为警告。如果添加

会发生什么
-std=c++98 -pedantic-errors

在构建可执行文件时到命令行?

答案 2 :(得分:2)

其他人已经谈到过One Definition Rule,我想我会想出一些解释和真正的解决方法。

解释

我不会解释一个定义规则,但我会解释为什么链接器不会抱怨。使用模板时,每个对象都会获得自己的std::vector<int>实例。链接器只需获取第一个可用的链接。

如果不是这种情况,则必须在一个源文件中显式实例化模板,然后在其他源文件中使用extern关键字......但只有Comeau支持它。

解决

由于我基本上假设您正在尝试实现指向实现的指针,除了转发之外,您没有太多选择。

之前已经处理过类似的问题(我不喜欢依赖Pimpl来简化依赖关系...),我只是依赖于命名约定,与我重用的命名空间结合起来实现细节:

namespace detail { class FooImpl; }

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

简单高效。我总是使用detail来获取实现细节,我只需将Impl附加到类名称Impl的末尾。

备注

  • 遗憾的是,你不能在课堂上向前宣布,但我们无能为力。
  • namespace details可以防止使用这些符号污染您所在的主命名空间,尤其是IDE自动完成时非常方便,因为否则您将FooFooImpl作为命题。< / LI>
  • ImplFoo对于自动填充效果不佳,因为所有pimpl都会以Impl开头!

希望有所帮助。