智能指针构造与析构
在smartPtr内存模型文章中,介绍了std::shared_ptr
和std::unique_ptr
的内存模型区别,这篇文章介绍它们的构造和析构的行为区别。
注意:本文只讨论使用默认删除器的情况,如果使用自定义删除器,则跟删除器的实现有关系,本文讨论的前提和结论可能都不再成立!
参考文章
std::unique_ptr may be constructed for an incomplete type T, such as to facilitate the use as a handle in the pImpl idiom. If the default deleter is used, T must be complete at the point in code where the deleter is invoked, which happens in the destructor, move assignment operator, and reset member function of std::unique_ptr. (Conversely, std::shared_ptr can’t be constructed from a raw pointer to incomplete type, but can be destroyed where T is incomplete).
If T is a derived class of some base B, then std::unique_ptr
is implicitly convertible to std::unique_ptr. The default deleter of the resulting std::unique_ptr will use operator delete for B, leading to undefined behavior unless the destructor of B is virtual. Note that std::shared_ptr behaves differently: std::shared_ptr will use the operator delete for the type T and the owned object will be deleted correctly even if the destructor of B is not virtual.
模板参数类型的完整性
std::shared_ptr
构造时要求模板参数类型是complete type,析构时没有此要求- 因为在
std::shared_ptr
构造的过程中需要创建Control Block,而在Control Block中需要存储被管理类型的析构函数 - 而在析构时,无论当前的
shared_ptr
存储的类型是否complete,它指向的Control Block肯定是有完整的deleter的,因为这是在shared_ptr
构造时就保证的
- 因为在
std::unique_ptr
构造时对模板参数类型没有要求,析构时要求必须是complete typeunique_ptr
没有Control Block,无需在构造时存储管理类型的deleter- 而在析构时,
unique_ptr
需要根据当前类型来调用管理类型的析构函数来释放资源
下面这个例子从侧面证明这一点:
#include <memory>
#include <iostream>
class Incomplete;
int main() {
Incomplete *raw;
/**
In file included from /usr/include/c++/11/bits/shared_ptr.h:53,
from /usr/include/c++/11/memory:77,
from incomplete_type.cpp:1:
/usr/include/c++/11/bits/shared_ptr_base.h: In instantiation of ‘std::__shared_ptr<_Tp, _Lp>::__shared_ptr(_Yp*) [with _Yp = Incomplete; <template-parameter-2-2> = void; _Tp = Incomplete; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]’:
/usr/include/c++/11/bits/shared_ptr.h:160:46: required from ‘std::shared_ptr<_Tp>::shared_ptr(_Yp*) [with _Yp = Incomplete; <template-parameter-2-2> = void; _Tp = Incomplete]’
incomplete_type.cpp:8:46: required from here
/usr/include/c++/11/bits/shared_ptr_base.h:1102:26: error: invalid application of ‘sizeof’ to incomplete type ‘Incomplete’
1102 | static_assert( sizeof(_Yp) > 0, "incomplete type" );
| ^~~~~~~~~~~
In file included from /usr/include/c++/11/memory:76,
from incomplete_type.cpp:1:
/usr/include/c++/11/bits/unique_ptr.h: In instantiation of ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Incomplete]’:
/usr/include/c++/11/bits/unique_ptr.h:361:17: required from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Incomplete; _Dp = std::default_delete<Incomplete>]’
incomplete_type.cpp:9:47: required from here
/usr/include/c++/11/bits/unique_ptr.h:83:23: error: invalid application of ‘sizeof’ to incomplete type ‘Incomplete’
83 | static_assert(sizeof(_Tp)>0,
| ^~~~~~~~~~~
*/
/**
* The following code will not compile because the type Incomplete is incomplete.
* But std::shared_ptr and std::unique_ptr fail for different reasons.
* As for std::shared_ptr, it requires the complete type of the template argument in constructor.
*
* As for std::unique_ptr, it requires the complete type of the template argument in destructor.
*/
std::shared_ptr<Incomplete> share_ptr(raw);
std::unique_ptr<Incomplete> unique_ptr(raw);
}
在pimpl设计模式unique_ptr incomplete type问题,对std::unique_ptr
的incomplete type
问题做了全面的分析。
析构行为
shared_ptr
无论如何都会正确的调用其管理对象的析构函数unique_ptr
不保证能正确的析构- 只有在被管理对象有继承关系,且析构是
virtual
时才能保证
- 只有在被管理对象有继承关系,且析构是
如下的例子说明这一点:
#include <memory>
#include <iostream>
struct A {
~A() {
std::cout << "A destructor\n";
}
};
struct B : public A {
~B() {
std::cout << "B destructor\n";
}
};
struct C {
virtual ~C() {
std::cout << "C destructor\n";
}
};
struct D : public C {
~D() override {
std::cout << "D destructor\n";
}
};
int main() {
{
std::cout << "When desctructor is non-virtual, std::shared_ptr will call correct destructor\n";
std::shared_ptr<A> a = std::make_shared<B>();
}
std::cout << "----" << std::endl;
{
std::cout << "When destructor is virtual, std::shared_ptr will call correct destructor\n";
std::shared_ptr<C> b = std::make_shared<D>();
}
std::cout << "----" << std::endl;
{
std::cout << "When destructor is non-virtual, std::unique_ptr will NOT call correct destructor\n";
/// This is undefined behavior, since B is destructed by A's destructor
std::unique_ptr<A> c = std::make_unique<B>();
}
std::cout << "----" << std::endl;
{
std::cout << "When destructor is virtual, std::unique_ptr will call correct destructor\n";
/// Since destructor is virtual, D's destructor will be called
std::unique_ptr<C> d = std::make_unique<D>();
}
}