【问题标题】:C++ linker - Lack of duplicate symbolsC++ 链接器 - 缺少重复符号
【发布时间】:2010-12-17 03:50:03
【问题描述】:

为什么下面的代码没有给出 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;
}

【问题讨论】:

    标签: c++ visual-studio gcc linker


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      生日,

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

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

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

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

      -std=c++98 -pedantic-errors
      

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

      【讨论】:

      • 添加这些标志不会添加任何警告,并且行为相同。所以显然 gcc 只是忽略了有两个相同符号的事实。我想知道是否有某种方法可以强制它显示任何重复项。与 -z muldefs 相反。我没有在手册中看到任何内容。
      • 重复符号并不总是错误,有时是必需的。
      • @R.佩特,同意。但是切换行为会很好,不是吗。
      【解决方案3】:

      其他人已经谈论过单一定义规则,我想我会加入一些解释和真正的解决方法。

      解释

      我不会解释单一定义规则,但我会解释为什么链接器不抱怨。当您使用模板时,每个对象都有自己的std::vector&lt;int&gt; 实例化。链接器只选择第一个可用的。

      如果不是这样,您必须在一个源文件中显式实例化模板,然后在其他源文件中使用 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 作为命题。
      • ImplFoo 对于自动补全也不是很好,因为所有 pimpl 都以 Impl 开头!

      希望对您有所帮助。

      【讨论】:

        猜你喜欢
        • 2017-02-25
        • 2015-02-10
        • 2016-06-04
        • 1970-01-01
        • 1970-01-01
        • 2011-02-26
        • 1970-01-01
        • 2012-10-04
        • 2014-01-07
        相关资源
        最近更新 更多