shared_ptr 和 unique_ptr 深入探秘

简介: shared_ptr 和 unique_ptr 深入探秘

C++ 中 shared_ptrunique_ptr 是 C++11 之后被广泛使用的两个智能指针,但是其实他们在使用上还是有一些“秘密”的,我根据平时遇到的两个问题,总结记录一些知识。


为什么 unique_ptr 需要明确知道类型的析构函数


这个问题是我写 unique_ptr 调试接口的时候才注意到的,之前确实不知道。为什么会这样呢?首先我们必须要知道 unique_ptr 到底封装了什么?通常 unique_ptr 就是简单的对裸指针封装,并且禁用拷贝和赋值:


template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;


可以看到,Deleter 的类型是 unique_ptr 类型的一部分。在 unique_ptr 内部会保存类型为 T*Deleter 的成员 ,分别表示保存的裸指针和删除器。假设内部是这么实现的 (一般会运用空基类优化把 Deleter 的空间优化掉,libstdc++ 里把他们放进了一个 tuple。这里是简化了):


private:  
T* p;  
Deleter del;


然后析构的时候就会这样:


~unique_ptr()  
{  
del(p);  
}


Deleter 是默认的 std::default_delete 时,del(p) 就会 delete pdelete 会调用析构函数。而 delete 一个不完整类型的指针是 ub(undefined behavior)。在典型的实现中都会在 delete 前通过 static_assert(sizeof(T) > 0) 做检查。 sizeof 对 incomplete type 求值会直接编译出错。

incomplete type 是指当定义一个变量的时候,不知道应该分配多少内存。C++ 声明和定义最大的区别就是是否发生内存分配,当发生内存分配的时候,必须知道要分配多少内存,通常一个未定义的 struct,未指定长度的数组类型,都会引发 incomplete type 的问题。


struct student;  
std::cout << sizeof(student) << std::endl;


只声明了结构体 student,但是并没有定义,所以是一个 incomplete type,所以 sizeof 无法执行。

回到 unique_ptr,现在我们知道 unique_ptr 的报错链路是 unique_ptr->delete->sizoef,也就是 sizeof 才是罪魁祸首。所以当 Deleter 非默认时,就不一定需要知道类型的析构函数。比如下面这样:


// A is incomplete type  
class A;  
auto Del = [] (A*) { };  
std::unique_ptr<A, decltype(Del)> ptr;


因此可以对这个问题做定性:并不是 unique_ptr 需要知道析构函数,而是 unique_ptr 的默认删除器 Deleter 需要明确知道类型的析构函数

继续深挖一下,这个问题会出现在 shared_ptr 吗?答案是不会。这又引入了另一个问题,shared_ptr 和 unique_ptr 的封装有什么不同?


shared_ptr 的封装


按理说 shared_ptr.reset 的时候需要 delete ptr 就需要 ptr 的类型(错了请指正),而 shared_ptr 的 template type 可以是 incomplete type(错误请指正)。cppreference 是这么描述的:

std::shared_ptr may be used with an incomplete typeT. However, the constructor from a raw pointer (template shared_ptr(Y )) and the templatevoid reset(Y) member function may only be called with a pointer to a complete type (note that std::unique_ptr may be constructed from a raw pointer to an incomplete type).

reset 的时候需要类型完整。默认构造的时候允许是不完整类型。为什么会这样呢?shared_ptr 怎么处理 Deleter 呢?(还记得吧, Deleter 就是智能指针析构时候的删除操作)

在常见编译器的实现里,shared_ptrDeleter(包括默认情况下的 operator delete)放进一个叫做 control block 的结构里,相当于做了一次 type erasure,把 Deleter 的类型从 shared_ptr 类型本身里面擦下去。Deleter 的类型在 control block 的具体类型上,shared_ptr 本身只持有一个 control block 基类的指针,通过虚函数来调用 Deleter。而因为 shared_ptr 构造的时候要求必须是 complete type,control block已经知道怎么析构了,shared_ptr 析构的时候就调用个虚函数,具体事情它不管的。

这下我们明白了,unique_ptr 的封装太简单了,没有 control block,Deleter(包括默认的std::default_delete)直接做在 unique_ptr 一起了,这就导致 unique_ptr 的析构函数需要亲手析构被管理的类型,因此析构函数必须看到 complete type。然而反过来,因为构建的时候只需要保存下指针,所以 unique_ptr 构造的时候不需要看到 complete type。这俩正好是反的。C++ 标准并没有规定这些实现细节,但是规定函数签名和特性的时候,是考虑着比较合理的实现方式来写标准的,到最后标准落下来之后也差不多只能这么实现了。


总结


  • unique_ptr 只保存了类型指针 ptr 和这个指针的析构方法,调用 delete ptr,就需要ptr的完整类型,为了防止这个问题出现,直接通过 assert sizeof 排除掉了这种风险。unique_ptr 相当于在编译时绑定了删除器
  • shared_ptr 保存的是一个控制块的指针。控制块包含的就是一个引用计数和一个原来对象的裸指针。控制块中初始化的指针是 nullptr,在运行时为其赋值,也可以通过 reset 修改。类似于虚函数,shared_ptr 相当于在运行时绑定了删除器

虽然只是一个小小的知识点,但是也帮助我深入理解了 shared_ptrunique_ptr 在设计上的区别,对于不同使用场景下选择不同智能指针的体会也更加深刻。

目录
相关文章
|
2月前
|
安全 C++ 开发者
C++ 11新特性之shared_ptr
C++ 11新特性之shared_ptr
18 0
|
4月前
|
存储 C++ 容器
云原生应用问题之使用std::unique_ptr和std::shared_ptr如何解决
云原生应用问题之使用std::unique_ptr和std::shared_ptr如何解决
23 1
|
5月前
|
设计模式 C++ 开发者
C++一分钟之-智能指针:unique_ptr与shared_ptr
【6月更文挑战第24天】C++智能指针`unique_ptr`和`shared_ptr`管理内存,防止泄漏。`unique_ptr`独占资源,离开作用域自动释放;`shared_ptr`通过引用计数共享所有权,最后一个副本销毁时释放资源。常见问题包括`unique_ptr`复制、`shared_ptr`循环引用和裸指针转换。避免这些问题需使用移动语义、`weak_ptr`和明智转换裸指针。示例展示了如何使用它们管理资源。正确使用能提升代码安全性和效率。
95 2
|
6月前
|
安全 Linux 编译器
从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr(下)
从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr
41 3
|
6月前
|
安全 编译器 C语言
从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr(中)
从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr
41 1
|
6月前
|
安全 编译器 C语言
从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr(上)
从C语言到C++_36(智能指针RAII)auto_ptr+unique_ptr+shared_ptr+weak_ptr
33 0
|
6月前
|
安全 编译器 C++
[C++] 智能指针(shared_ptr、unique_ptr)
[C++] 智能指针(shared_ptr、unique_ptr)
69 1
|
6月前
|
安全 编译器 C++
智能指针shared_ptr、unique_ptr、weak_ptr
智能指针shared_ptr、unique_ptr、weak_ptr
168 0
|
6月前
|
C++
C++智能指针shared_ptr
C++智能指针shared_ptr
47 0
|
6月前
|
C++
C++智能指针unique_ptr
C++智能指针unique_ptr
27 0