实现 pimpl idiom 时出现链接器错误

Posted

技术标签:

【中文标题】实现 pimpl idiom 时出现链接器错误【英文标题】:Linker error while implementing pimpl idiom 【发布时间】:2011-09-30 21:26:52 【问题描述】:

对提供者进行了更清晰的编辑。很抱歉让大家感到困惑。

这是在 Windows 下。

我有一个使用 pimpl 习惯用法实现类的静态库。 pimpl 标头不仅由消费代码使用,而且还链接到静态库。然而,当我编译消费代码 (.exe) 时,链接器会抱怨 pimpl 标头应该隐藏的 实现类 上未解析的外部。

这怎么可能?

// Foo.lib

// Foo.h

class FooImpl;

class Foo

    std::shared_ptr<FooImpl> pimpl_;    
public:
    Foo();
;

// Foo.cpp

#include "Foo.h"
#include "FooImpl.h"

Foo::Foo() : pimpl_(new FooImpl())



// This is how its used in Bar.exe
// Bar.exe links against Foo.lib

// Bar.h

#include "Foo.h"

class Bar

    Foo access_foo_;
;

// Bar.cpp

#include "Bar.h"

当我编译/链接 Bar.exe 时,链接器抱怨它无法解析 FooImpl。我忘记了它到底说了什么,因为我现在无法访问我的工作电脑,但这就是它的要点。这个错误对我来说没有意义,因为走 pimpl 路线的目的是让 Bar.exe 不必担心 FooImpl。

确切的错误是这样的:

1>Foo.lib(Foo.obj) : error LNK2019: unresolved external symbol "public: __thiscall FooImpl::FooImpl(void)" (??0FooImpl@@QAE@XZ) 在函数“public: __thiscall Foo”中引用::Foo(void)" (??0Foo@@QAE@XZ)

【问题讨论】:

第3行有错误;它应该说“foo”,而不是“bar”。 你在这里没有给我们太多机会 - 告诉我们你尝试了什么,或者向我们展示一些编译器输出,或者向我们展示你在文件顶部包含的内容,或者如果您使用了makefile,可能会显示makefile?我们可能可以帮助他们:) 【参考方案1】:

当您创建静态库时,链接器不会尝试解决所有缺失的问题;它假定您稍后会将其链接到另一个库,或者可执行文件本身将提供一些缺失的功能。您一定忘记在库项目中包含一些关键的实现文件。

另一种可能是pimpl实现类是一个模板类。模板不会立即生成代码,编译器会一直等待,直到您尝试使用它们并填充模板参数。您的实现文件必须包含模板的实例化以及您的库将支持的参数。


看到您的编辑后,问题是 Foo::Foo 构造函数需要访问 FooImpl::FooImpl 构造函数,但链接器不知道在哪里找到它。当链接器将库放在一起时,它不会尝试解析所有引用,因为它可能依赖于另一个库。唯一需要解决所有问题的时间就是将可执行文件放在一起。所以即使 Bar 不需要直接了解 FooImpl,它仍然对它有依赖。

您可以通过以下两种方式之一解决此问题:要么将 FooImpl 与 Foo 一起从库中导出,要么确保 Foo 在编译时可以访问 FooImpl,方法是将它们都放在 Foo.cpp 中,而 FooImpl 位于 Foo 之前。

【讨论】:

为了更清楚,我编辑了我的问题。 FWIW,我的 pimpl 类不是模板。 @Dilip,你正在成为我的第一个断言的牺牲品——你的 Foo::Foo 无法访问 FooImpl::FooImpl。要么确保 FooImpl::FooImpl 从库中导出,要么在 Foo::Foo 之前将其放入 Foo.cpp。 非常感谢!我想我现在明白了。我明天上班时试试这个。 在 Foo.cpp 中包含 FooImpl 解决了链接器错误。我不想导出,因为从静态库中这样做有点违反直觉【参考方案2】:

1>Foo.lib(Foo.obj) : error LNK2019: unresolved external symbol "public: __thiscall FooImpl::FooImpl(void)" (??0FooImpl@@QAE@XZ) 在函数“public: __thiscall Foo”中引用::Foo(void)" (??0Foo@@QAE@XZ)

链接器抱怨你找不到FooImpl::FooImpl() 的定义。你在这里调用构造函数-

Foo::Foo() : pimpl_(new FooImpl())
                    // ^^^^^^^^^ Invoking the default constructor.

您只是提供了默认构造函数的声明,但不是定义

【讨论】:

我不明白。构建 Foo.lib 时不会发生此错误。这发生在我构建 Bar.exe(链接到 Foo.lib)时。这让我感到困惑。 Foo pimpl 类存在的主要原因是 Bar.exe 不必依赖 FooImpl,对吗? 我可能误用了编译防火墙的习语来实现接口-实现分离。我只为 Bar.exe 提供 Foo.h,并允许 Bar.exe 链接到 Foo.lib,希望这样可以避免 Bar.exe 担心 FooImpl。我好像弄错了? "I don't understand. This error is not happening when I build Foo.lib." 可能在您构建的文件中,您无法实例化调用默认构造函数的FooImpl

以上是关于实现 pimpl idiom 时出现链接器错误的主要内容,如果未能解决你的问题,请参考以下文章

升级到 XCTest 时出现链接器错误

实例化模板类时出现链接器错误

包含omniture库时出现链接器错误

重载抽象运算符 = 时出现 Clang 链接器错误

使用 std::unique_ptr 的 C++ Pimpl Idiom 不完整类型

C++: The PIMPL idiom