从场景解析 C++ shared_from_this
Contents
智能指针之shared_ptr易错点05。
掌握C++ 智能指针的自我引用:深入解析 shared_from_this
和 weak_from_this
。
C++之shared_from_this用法以及类自引用this指针陷阱。
思考:
设计一个树的节点的时候,如果使用智能指针:用一个
std::vector<shared_ptr<TreeNode>>
来存储子节点,为避免循环引用,用weak_ptr<TreeNode>
来存储自身的父节点指针。
那添加子节点的时候,怎么把自身的shared_ptr
赋值给子节点存储的父节点指针呢?
两个错误做法:
- 使用
std::make_shared<TreeNode<T>>(*this)
来创建一个新的shared_ptr
,然后赋值给子节点存储的父节点指针。
|
|
使用 std::make_shared<TreeNode<T>>(*this)
时,实际上是对当前对象的 拷贝构造(调用拷贝构造函数)来创建一个新的 TreeNode<T>
对象。这意味着你将当前节点的状态(但不是智能指针)拷贝到一个新的对象中,而新对象的生命周期由 std::shared_ptr
管理。
- 使用
std::shared_ptr<TreeNode<T>> ptr(this)
把裸指针this
包装为shared_ptr
。
将 this
传递给 std::shared_ptr<TreeNode<T>>
会导致新创建的 shared_ptr
管理一个裸指针,而裸指针的生命周期没有由智能指针控制。
引出一个常规问题,从裸指针创建 shared_ptr
的隐患:
- 当
shared_ptr
的引用计数归零时,它会释放它所管理的对象。如果裸指针在此时继续存在,它仍然会指向原来的内存地址。但这时该内存已被释放,裸指针成为了悬空指针,也就是所谓的野指针。 - 如果裸指针指向的内存已经被释放(例如,该指针原本由
delete
或delete[]
释放),然后你用这个裸指针创建shared_ptr
,那么shared_ptr
仍然会管理这个已经释放的内存区域。这会导致访问已释放内存(悬空指针)或双重释放内存的问题(如果shared_ptr
销毁时再次释放内存)。 - 裸指针可能指向一个栈上的对象:如果裸指针指向一个栈上分配的对象,并且你用它创建
shared_ptr
,那么shared_ptr
会试图在引用计数归零时释放这个栈上对象的内存。然而,栈上对象的生命周期由栈帧的销毁来管理,而shared_ptr
并不清楚这一点。这将导致程序的未定义行为。
案例:
|
|
如何会导致shared_ptr
指向同一个对象,但是不共享引用计数器?
是因为裸指针与shared_ptr
混用,如果我们用一个裸指针初始化或者赋值给shared_ptr
指针时,在shared_ptr
内部生成一个计数器,当另外一个shared_ptr
不用share_ptr
赋值或者初始化的话,再次将一个裸指针赋值给另外一个shared_ptr
时,又一次生成一个计数器,两个计数器不共享。
shared_ptr
实现原理:
shared_ptr
从 _Ptr_base
继承了 element_type
和 _Ref_count_base
类型的两个成员变量。
|
|
_Ref_count_base
中定义了原子类型的变量 _Uses
和 _Weaks
,它们分别记录资源的引用个数和资源观察者的个数。
|
|
从 this
构造智能指针的正确做法
|
|
shared_from_this
是 C++11 中引入的功能,允许对象在继承了 std::enable_shared_from_this
的情况下,安全地生成自身的 std::shared_ptr
实例,而不会创建新的控制块(reference counting block)。这样可以避免悬垂指针的问题,特别是在对象的成员函数中使用时,可以确保对象在使用期间不被销毁。
std::enable_shared_from_this<T>
内部维护了一个 std::weak_ptr<T>
。当第一个 std::shared_ptr<T>
开始管理该对象时,这个 weak_ptr
被初始化。之后,当 shared_from_this()
被调用时,它将基于这个已经存在的 weak_ptr
返回一个新的 std::shared_ptr<T>
,这个新的 shared_ptr
与原有的 shared_ptr
共享对对象的所有权。
|
|
实践:
实现这个 TreeNode
类的时候,shared_from_this
解析不出来(似乎是因为模板导致的 clangd 语法解析失败)。
改为 this->shared_from_this()
后报错消失,因为 shared_from_this
实际上是当前父类 enable_shared_from_this
的成员函数。
最终实现:
|
|