智能指针之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 的成员函数。
最终实现:
| |

