是否需要std :: unique_ptr 知道T的完整定义?

| 我在标头中有一些代码,如下所示:
#include <memory>

class Thing;

class MyClass
{
    std::unique_ptr< Thing > my_thing;
};
如果我将此头文件包含在不包含“ 1”类型定义的cpp中,则无法在VS2010-SP1下进行编译:   1> C:\\ Program档案(x86)\\ Microsoft   视觉工作室   10.0 \\ VC \\ include \\ memory(2067):错误C2027:使用未定义类型\'Thing \' 将
std::unique_ptr
替换为
std::shared_ptr
,然后编译。 因此,我猜测这是当前VS2010 2的实现,需要完整的定义,并且完全依赖于实现。 还是?它的标准要求中是否有某些内容无法使ѭ2的实现仅与前向声明一起使用?感觉很奇怪,因为它只应持有一个指向ѭ1的指针,不是吗?     
已邀请:
从这里通过。 C ++标准库中的大多数模板都要求使用完整的类型实例化它们。但是,
shared_ptr
unique_ptr
是部分例外。某些(但不是全部)成员可以使用不完整的类型实例化。这样做的动机是使用智能指针来支持pimpl等惯用语言,而又不会冒未定义行为的风险。 当您使用不完整的类型并在其上调用
delete
时,可能会发生不确定的行为:
class A;
A* a = ...;
delete a;
以上是法律法规。它将编译。您的编译器可能会或不会针对上述代码发出警告。当它执行时,可能会发生坏事。如果您很幸运,您的程序将崩溃。但是,更可能的结果是您的程序将以静默方式泄漏内存,因为不会调用
~A()
。 在上面的示例中使用
auto_ptr<A>
没有帮助。您仍然会得到与使用原始指针相同的未定义行为。 但是,在某些地方使用不完整的类非常有用!
shared_ptr
unique_ptr
在这里提供帮助。使用这些智能指针之一可以使您摆脱不完整的类型,除非需要具有完整的类型。最重要的是,当有必要使用完整类型时,如果此时尝试将智能指针与不完整类型一起使用,则会出现编译时错误。 没有更多未定义的行为: 如果您的代码可以编译,那么您在需要使用的所有地方都使用了完整类型。
class A
{
    class impl;
    std::unique_ptr<impl> ptr_;  // ok!

public:
    A();
    ~A();
    // ...
};
shared_ptr
unique_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>()
的调用时,
unique_ptr<A>{A*}
构造函数才能摆脱不完整的
A
。例如,如果将
unique_ptr
放在堆上,则可以得到不完整的
A
。关于这一点的更多详细信息可以在BarryTheHatchet的答案中找到。     
编译器需要Thing的定义才能为MyClass生成默认的析构函数。如果您明确声明了析构函数并将其(空)实现移动到CPP文件,则应编译代码。     
这与实现无关。之所以起作用,是因为
shared_ptr
确定了在运行时要调用的正确析构函数-它不是类型签名的一部分。但是,ѭ8的析构函数是其类型的一部分,必须在编译时知道。     
看起来当前的答案并不能完全确定为什么默认构造函数(或析构函数)有问题,而cpp中声明的空问题却没有。 这是怎么回事: 如果外部类(即MyClass)没有构造函数或析构函数,则编译器将生成默认的构造函数或析构函数。这样做的问题是,编译器实际上是在.hpp文件中插入默认的空构造函数/析构函数。这意味着默认构造函数/析构函数的代码将与主机可执行文件的二进制文件一起编译,而不是与库的二进制文件一起编译。但是,此定义不能真正构造子类。因此,当链接程序进入您库的二进制文件并尝试获取构造函数/析构函数时,找不到任何链接,并且会出现错误。如果构造函数/析构函数代码位于.cpp中,则您的库二进制文件具有可用于链接的代码。 这与使用unique_ptr或shared_ptr无关,其他答案似乎也可能使旧VC ++中的unique_ptr实现混淆(VC ++ 2015在我的机器上工作正常)。 因此,故事的寓意是您的标头必须保持不受任何构造函数/析构函数定义的约束。它只能包含其声明。例如,hpp中的
~MyClass()=default;
将不起作用。如果允许编译器插入默认构造函数或析构函数,则会出现链接器错误。 另一个注意事项:如果即使在cpp文件中有构造函数和析构函数后仍然出现此错误,则最有可能的原因是您的库未正确编译。例如,有一次我只是将项目类型从VC ++中的“控制台”更改为“库”,但由于VC ++未添加_LIB预处理程序符号而产生了完全相同的错误消息,因此出现了此错误。     
仅出于完整性考虑: 标头: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再次被销毁,这是有道理的。)     
在模板实例化时,需要Thing的完整定义。这就是pimpl习惯用法编译的确切原因。 如果不可能,人们不会问这样的问题。     
简单的答案是只使用shared_ptr。     
至于我
QList<QSharedPointer<ControllerBase>> controllers;
只包括标题...
#include <QSharedPointer>
    

要回复问题请先登录注册