我在标题中有一些代码如下:
#include <memory>
class Thing;
class MyClass
{
std::unique_ptr< Thing > my_thing;
};
如果我在不包含Thing
类型定义的cpp中包含此标头,那么这不会在VS2010-SP1下编译:
1&gt; C:\ Program Files(x86)\ Microsoft 视觉工作室 10.0 \ VC \ include \ memory(2067):错误C2027:使用未定义类型'Thing'
将std::unique_ptr
替换为std::shared_ptr
并进行编译。
所以,我猜这是当前VS2010 std::unique_ptr
的实现,它需要完整的定义,并且完全取决于实现。
或者是吗?它的标准要求中是否有某些内容使std::unique_ptr
的实现无法仅使用前向声明?这感觉很奇怪,因为它只应该指向Thing
,不是吗?
答案 0 :(得分:301)
通过here。
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>{A*}
的调用时,A
构造函数才能使用不完整的~unique_ptr<A>()
。例如,如果将unique_ptr
放在堆上,则可以使用不完整的A
。有关此问题的更多详细信息,请参阅BarryTheHatchet's answer here。
答案 1 :(得分:41)
编译器需要Thing的定义来生成MyClass的默认析构函数。如果显式声明析构函数并将其(空)实现移动到CPP文件,则代码应该编译。
答案 2 :(得分:13)
这不依赖于实现。它起作用的原因是因为shared_ptr
确定了在运行时调用的正确析构函数 - 它不是类型签名的一部分。但是,unique_ptr
的析构函数是的一部分类型,必须在编译时知道。
答案 3 :(得分:5)
看起来当前的答案并没有确切地说明为什么默认构造函数(或析构函数)存在问题,但在cpp中声明的空元素不是。
以下是发生的事情:
如果外部类(即MyClass)没有构造函数或析构函数,则编译器会生成默认值。这个问题是编译器实际上在.hpp文件中插入了默认的空构造函数/析构函数。这意味着默认的contructor /析构函数的代码与主机可执行文件的二进制文件一起编译,而不是与库的二进制文件一起编译。但是,这个定义不能真正构建分部类。所以当链接器进入你的库的二进制文件并尝试获取构造函数/析构函数时,它找不到任何错误。如果构造函数/析构函数代码在.cpp中,那么您的库二进制文件可用于链接。
这与使用unique_ptr或shared_ptr无关,而其他答案似乎可能会混淆旧VC ++中针对unique_ptr实现的错误(VC ++ 2015在我的机器上运行正常)。
故事的寓意是你的标题需要保持不受任何构造函数/析构函数的定义。它只能包含他们的声明。例如,hpp中的~MyClass()=default;
将不起作用。如果允许编译器插入默认构造函数或析构函数,则会出现链接器错误。
另一个注意事项:如果在cpp文件中有构造函数和析构函数后仍然出现此错误,那么很可能原因是您的库未正确编译。例如,有一次我只是在VC ++中将项目类型从Console改为Library,我得到了这个错误,因为VC ++没有添加_LIB预处理器符号,并且产生完全相同的错误消息。
答案 4 :(得分:2)
为了完整性:
标题: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,这是有道理的。)
答案 5 :(得分:1)
在模板实例化时需要完整定义Thing。这就是为什么pimpl成语编译的确切原因。
如果不可能,人们就不会问this这样的问题。
答案 6 :(得分:0)
简单的答案是只使用shared_ptr。
答案 7 :(得分:-1)
我一直在寻找一种将PIMPL习惯用法与std::unique_ptr
一起使用的方法。 This guide是很好的资源。
简而言之,您可以采取以下措施使其正常工作:
#include <memory>
class Thing;
class MyClass
{
~MyClass(); // <--- Added
std::unique_ptr< Thing > my_thing;
};
MyClass::~MyClass() = default; // Or a custom implementation
答案 8 :(得分:-4)
对于我来说
QList<QSharedPointer<ControllerBase>> controllers;
只需包含标题...
#include <QSharedPointer>