我在标题中有一些代码,看起来像这样:

#include <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};

如果我在一个不包括Thing类型定义的cpp中包含这个头,那么这不会在VS2010-SP1下编译:

1>C:\程序文件(x86)\微软 Visual Studio 10.0\VC\include\memory(2067):错误C2027:使用未定义的类型'Thing'

将std::unique_ptr替换为std::shared_ptr并进行编译。

所以,我猜是当前的VS2010 std::unique_ptr的实现需要完整的定义,它完全依赖于实现。

真的是这样吗?它的标准要求中是否有一些东西使得std::unique_ptr的实现不可能只使用前向声明?这感觉很奇怪,因为它应该只持有一个指向Thing的指针,不是吗?


当前回答

我正在寻找一种方法来使用PIMPL习语与std::unique_ptr。这个指南是一个很好的资源。

简而言之,以下是你可以做的事情:

my_class.h

#include <memory>

class Thing;

class MyClass
{
    ~MyClass(); // <--- Added
    std::unique_ptr< Thing > my_thing;
};

my_class.cpp

MyClass::~MyClass() = default; // Or a custom implementation

其他回答

编译器需要Thing的定义来为MyClass生成默认析构函数。如果显式声明析构函数并将其(空)实现移动到CPP文件,则代码应该被编译。

在模板实例化时需要Thing的完整定义。这就是为什么要编译pimpl习语的确切原因。

如果不可能,人们就不会问这样的问题。

为了完整起见:

标题:A.h

class B; // forward declaration

class A
{
    std::unique_ptr<B> ptr_;  // ok!  
public:
    A();
    ~A();
    // ...
};

源A.cpp:

class B {  ...  }; // class definition

A::A() { ... }
A::~A() { ... }

类B的定义必须被构造函数、析构函数和任何可能隐式删除B的对象看到。 (虽然构造函数没有出现在上面的列表中,但在VS2017中,即使构造函数也需要b的定义。当考虑到构造函数中的异常情况时,unique_ptr将再次被销毁,这是有意义的。)

看起来目前的答案并没有完全确定为什么默认构造函数(或析构函数)是问题,而在cpp中声明的空构造函数不是问题。

事情是这样的:

If outer class (i.e. MyClass) doesn't have constructor or destructor then compiler generates the default ones. The problem with this is that compiler essentially inserts the default empty constructor/destructor in the .hpp file. This means that the code for default contructor/destructor gets compiled along with host executable's binary, not along with your library's binaries. However this definitions can't really construct the partial classes. So when linker goes in your library's binary and tries to get constructor/destructor, it doesn't find any and you get error. If the constructor/destructor code was in your .cpp then your library binary has that available for linking.

这与使用unique_ptr或shared_ptr和其他答案似乎是旧vc++的unique_ptr实现中可能令人困惑的错误(vc++ 2015在我的机器上工作良好)。

所以这个故事的寓意是你的头文件需要保持不受任何构造函数/析构函数定义的影响。它只能包含他们的声明。例如,~MyClass()=default;在HPP行不通。如果允许编译器插入默认构造函数或析构函数,则会得到链接器错误。

另一个旁注:如果在cpp文件中有构造函数和析构函数后仍然出现这个错误,那么很可能是库没有正确编译。例如,有一次我在vc++中简单地将项目类型从控制台更改为库,我得到了这个错误,因为vc++没有添加_LIB预处理器符号,这产生了完全相同的错误消息。

我正在寻找一种方法来使用PIMPL习语与std::unique_ptr。这个指南是一个很好的资源。

简而言之,以下是你可以做的事情:

my_class.h

#include <memory>

class Thing;

class MyClass
{
    ~MyClass(); // <--- Added
    std::unique_ptr< Thing > my_thing;
};

my_class.cpp

MyClass::~MyClass() = default; // Or a custom implementation