我在标题中有一些代码,看起来像这样:
#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的指针,不是吗?
从这里收养的。
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();
// ...
};
unique_ptr和shared_ptr的类型完整性要求
Shared_ptr和unique_ptr在不同的位置需要一个完整的类型。原因是模糊的,与动态删除器和静态删除器有关。具体原因并不重要。事实上,在大多数代码中,确切地知道在哪里需要一个完整的类型并不重要。只是代码,如果你写错了,编译器会告诉你。
但是,为了对您有所帮助,这里有一个表,其中记录了shared_ptr和unique_ptr在完整性要求方面的几个操作。
Operation |
unique_ptr |
shared_ptr |
P() (default constructor) |
incomplete |
incomplete |
P(const P&) (copy constructor) |
— |
incomplete |
P(P&&) (move constructor) |
incomplete |
incomplete |
~P() (destructor) |
complete |
incomplete |
P(A*) (constructor from ptr) |
incomplete |
complete |
operator=(const P&) (copy assignment) |
— |
incomplete |
operator=(P&&) (move assignment) |
complete |
incomplete |
reset() |
complete |
incomplete |
reset(A*) |
complete |
complete |
任何需要指针转换的操作都需要unique_ptr和shared_ptr的完整类型。
只有当编译器不需要设置对~unique_ptr<A>()的调用时,unique_ptr<A>{A*}构造函数才能使用不完整的A。例如,如果你把unique_ptr放在堆上,你可以得到一个不完整的a。关于这一点的更多细节可以在BarryTheHatchet的回答中找到。
从这里收养的。
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();
// ...
};
unique_ptr和shared_ptr的类型完整性要求
Shared_ptr和unique_ptr在不同的位置需要一个完整的类型。原因是模糊的,与动态删除器和静态删除器有关。具体原因并不重要。事实上,在大多数代码中,确切地知道在哪里需要一个完整的类型并不重要。只是代码,如果你写错了,编译器会告诉你。
但是,为了对您有所帮助,这里有一个表,其中记录了shared_ptr和unique_ptr在完整性要求方面的几个操作。
Operation |
unique_ptr |
shared_ptr |
P() (default constructor) |
incomplete |
incomplete |
P(const P&) (copy constructor) |
— |
incomplete |
P(P&&) (move constructor) |
incomplete |
incomplete |
~P() (destructor) |
complete |
incomplete |
P(A*) (constructor from ptr) |
incomplete |
complete |
operator=(const P&) (copy assignment) |
— |
incomplete |
operator=(P&&) (move assignment) |
complete |
incomplete |
reset() |
complete |
incomplete |
reset(A*) |
complete |
complete |
任何需要指针转换的操作都需要unique_ptr和shared_ptr的完整类型。
只有当编译器不需要设置对~unique_ptr<A>()的调用时,unique_ptr<A>{A*}构造函数才能使用不完整的A。例如,如果你把unique_ptr放在堆上,你可以得到一个不完整的a。关于这一点的更多细节可以在BarryTheHatchet的回答中找到。