采用自这里。
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_;
public:
A();
~A();
};
为unique_ptr
和shared_ptr
指定类型完整性要求
shared_ptr
和unique_ptr
在不同的位置需要完整的类型。原因很难理解,与动态删除器和静态删除器有关。确切的原因并不重要。事实上,在大多数代码中,你并不需要知道完整类型所需的确切位置。只需编写代码,如果出错,编译器会告诉你。
然而,如果对你有帮助,这里有一个表格,记录了shared_ptr
和unique_ptr
的几个操作与完整性要求相关的信息。
操作 |
unique_ptr |
shared_ptr |
P() (默认构造函数) |
不完整的 |
不完整的 |
P(const P&) (复制构造函数) |
— |
不完整的 |
P(P&&) (移动构造函数) |
不完整的 |
不完整的 |
~P() (析构函数) |
完整的 |
不完整的 |
P(A*) (从指针构造函数) |
不完整的 |
完整的 |
operator=(const P&) (复制赋值运算符) |
— |
不完整的 |
operator=(P&&) (移动赋值运算符) |
完整的 |
不完整的 |
reset() |
完整的 |
不完整的 |
reset(A*) |
完整的 |
完整的 |
任何需要指针转换的操作都需要完整的类型,即对于
unique_ptr
和
shared_ptr
都是如此。
只有在编译器不需要设置调用
~unique_ptr<A>()
时,
unique_ptr<A>{A*}
构造函数才能使用不完整的
A
。例如,如果将
unique_ptr
放在堆上,则可以使用不完整的
A
。关于这一点的更多细节可以在
BarryTheHatchet的答案
here中找到。
shared_ptr
/unique_ptr
》,链接为http://home.roadrunner.com/~hinnant/incomplete.html。文章末尾的表格可以回答你的问题。 - James McNellis