281

我在标题中有一些代码,如下所示:

#include <memory>

class Thing;

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

如果我在不包含Thing类型定义的 cpp 中包含此标头,则在 VS2010-SP1 下无法编译:

1>C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\include\memory(2067): error C2027: use of undefined type 'Thing'

替换std::unique_ptrstd::shared_ptr并编译。

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

或者是吗?它的标准要求中是否有一些东西使得std::unique_ptr' 的实现无法仅使用前向声明?感觉很奇怪,因为它应该只保存一个指向 的指针Thing,不是吗?

4

9 回答 9

374

这里采用。

C++ 标准库中的大多数模板都要求使用完整类型对其进行实例化。然而shared_ptrunique_ptr部分例外。一些,但不是所有的成员都可以用不完整的类型来实例化。这样做的动机是支持使用智能指针的惯用语,例如pimpl,并且不会冒未定义行为的风险。

当您有一个不完整的类型并调用delete它时,可能会发生未定义的行为:

class A;
A* a = ...;
delete a;

以上为法典。它会编译。您的编译器可能会也可能不会针对上述代码发出警告。当它执行时,很可能会发生不好的事情。如果你很幸运,你的程序会崩溃。然而,更可能的结果是您的程序将~A()不会被调用而默默地泄漏内存。

在上面的例子中使用auto_ptr<A>没有帮助。您仍然会得到与使用原始指针相同的未定义行为。

然而,在某些地方使用不完整的类是非常有用的!这是哪里shared_ptrunique_ptr帮助。使用这些智能指针之一将使您摆脱不完整的类型,除非需要具有完整的类型。最重要的是,当需要一个完整的类型时,如果您尝试使用具有不完整类型的智能指针,则会出现编译时错误。

没有更多未定义的行为:

如果您的代码可以编译,那么您在任何需要的地方都使用了完整的类型。

class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};

shared_ptrunique_ptr要求在不同的地方有一个完整的类型。原因不明,与动态删除器与静态删除器有关。确切的原因并不重要。事实上,在大多数代码中,确切地知道需要完整类型的位置并不重要。只是代码,如果你弄错了,编译器会告诉你。

shared_ptr但是,如果它对您有帮助,这里有一个表格,其中记录了unique_ptr完整性要求的几个成员。如果成员需要一个完整的类型,那么条目有一个“C”,否则表格条目用“I”填充。

Complete type requirements for unique_ptr and shared_ptr

                            unique_ptr       shared_ptr
+------------------------+---------------+---------------+
|          P()           |      I        |      I        |
|  default constructor   |               |               |
+------------------------+---------------+---------------+
|      P(const P&)       |     N/A       |      I        |
|    copy constructor    |               |               |
+------------------------+---------------+---------------+
|         P(P&&)         |      I        |      I        |
|    move constructor    |               |               |
+------------------------+---------------+---------------+
|         ~P()           |      C        |      I        |
|       destructor       |               |               |
+------------------------+---------------+---------------+
|         P(A*)          |      I        |      C        |
+------------------------+---------------+---------------+
|  operator=(const P&)   |     N/A       |      I        |
|    copy assignment     |               |               |
+------------------------+---------------+---------------+
|    operator=(P&&)      |      C        |      I        |
|    move assignment     |               |               |
+------------------------+---------------+---------------+
|        reset()         |      C        |      I        |
+------------------------+---------------+---------------+
|       reset(A*)        |      C        |      C        |
+------------------------+---------------+---------------+

任何需要指针转换的操作都需要unique_ptr和的完整类型shared_ptr

仅当编译unique_ptr<A>{A*}A不需要设置对~unique_ptr<A>(). 例如,如果您将unique_ptr放在堆上,您可以逃脱不完整的A. 关于这一点的更多细节可以在BarryTheHatchet 的回答中找到

于 2011-05-22T15:54:34.967 回答
50

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

于 2011-05-16T00:17:11.143 回答
20

这不依赖于实现。它起作用的原因是因为shared_ptr确定了在运行时调用的正确析构函数——它不是类型签名的一部分。但是,unique_ptr的析构函数其类型的一部分,并且必须在编译时知道。

于 2011-05-22T14:03:29.153 回答
12

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

这是发生了什么:

如果外部类(即 MyClass)没有构造函数或析构函数,则编译器会生成默认值。这样做的问题是编译器本质上在 .hpp 文件中插入了默认的空构造函数/析构函数。这意味着默认构造函数/析构函数的代码与主机可执行文件的二进制文件一起编译,而不是与库的二进制文件一起编译。然而,这个定义并不能真正构造部分类。因此,当链接器进入您的库的二进制文件并尝试获取构造函数/析构函数时,它找不到任何内容并且您会收到错误消息。如果构造函数/析构函数代码在您的 .cpp 中,那么您的库二进制文件可用于链接。

这与使用 unique_ptr 或 shared_ptr 无关,其他答案似乎可能会混淆旧 VC++ 中用于 unique_ptr 实现的错误(VC++ 2015 在我的机器上运行良好)。

这个故事的寓意是你的标题需要保持没有任何构造函数/析构函数定义。它只能包含他们的声明。例如,~MyClass()=default;在 hpp 中不起作用。如果您允许编译器插入默认构造函数或析构函数,您将收到链接器错误。

另一个注意事项:如果即使在 cpp 文件中有构造函数和析构函数后仍然出现此错误,则很可能原因是您的库未正确编译。例如,有一次我只是在 VC++ 中将项目类型从控制台更改为库,我得到了这个错误,因为 VC++ 没有添加 _LIB 预处理器符号,并且产生了完全相同的错误消息。

于 2017-02-10T11:45:21.587 回答
9

只是为了完整性:

标题:啊

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 会再次被销毁。)

于 2018-01-12T15:31:48.723 回答
1

在模板实例化时需要对事物的完整定义。这就是 pimpl idiom 编译的确切原因。

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

于 2011-05-22T13:57:18.053 回答
0

我正在寻找一种将 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
于 2020-10-03T18:11:15.247 回答
-5

简单的答案就是使用 shared_ptr 代替。

于 2019-06-07T01:54:49.213 回答
-9

至于我,

QList<QSharedPointer<ControllerBase>> controllers;

只需包含标题...

#include <QSharedPointer>
于 2018-08-22T06:37:14.633 回答