C++链接器 - 重复符号缺失

5
为什么以下代码不会给我一个重复符号链接器错误的Impl?
我在继承的一些代码中遇到了这个问题,为简单起见,在此重新创建了一个更短的版本。
我有两个类Foo和Bar,它们分别在各自的.cpp文件中定义了相同结构体Impl的不同版本。因此,Foo.cpp和Bar.cpp都有一个相同命名的Impl定义,但每个定义都有不同的内联构造函数实现。
Foo和Bar都有一个Impl类型的成员变量,并且在其.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个回答

4
你正在违反单一定义规则,而编译器/链接器没有必要告诉你这一点。

3

你好,

默认的链接编辑器行为是找到符合要求的第一个符号并停止搜索。

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

编辑:我刚刚发现Solaris上的链接编辑器默认情况下不允许多个定义。实际上,您必须使用链接编辑器开关“-z muldefs”才能允许在用于建立可执行文件闭包的对象中出现多个定义。

编辑2:我对此很感兴趣,因为这应该被标记为警告。如果您添加了什么会发生呢?

-std=c++98 -pedantic-errors

当您构建可执行文件时,是否需要将其添加到命令行中?


添加这些标志不会增加任何警告,并且它的行为也是相同的。因此,显然gcc忽略了存在两个相同符号的事实。我想知道是否有一种方法可以强制它显示任何重复项。有点像-z muldefs的反面。我在手册中没有看到任何内容。 - Bob Mourlam
重复的符号并不总是错误,有时是必需的。 - Roger Pate
@R. Pate,同意。但是切换行为会很好,不是吗? - Rob Wells

2

其他人已经谈论了“一次定义规则”,我想解释一下并提供一个真正的解决方法。

解释:

我不会解释“一次定义规则”,但我会解释为什么链接器不会报错。当您使用模板时,每个对象都会得到自己的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注释:
  • 可惜你不能在类中仅仅声明它,但我们无能为力。
  • namespace details可以防止污染主命名空间,避免IDE自动完成时出现FooFooImpl等重复建议。
  • ImplFoo也不太适合自动完成,因为所有pimpl都以Impl开头!
希望这有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接