调用std::shared_ptr方法时引用计数变化及对象异常销毁问题
你对this指针和std::shared_ptr引用计数关系的判断一点没错:this就是个裸指针,它的存在和任何shared_ptr的引用计数都没有关联。
在你的代码里,当深层递归触发myMap.insert({this,5})时,这里会隐式把this裸指针构造成一个新的std::shared_ptr<A>。这个新的shared_ptr根本不知道之前的pA已经在管理同一个对象——它会认为自己是第一个接管该对象生命周期的智能指针。所以当外层的pA出作用域销毁时,它会把引用计数减到0,直接销毁对象;而myMap里的那个shared_ptr就变成了悬空指针,指向已经被释放的内存,自然就出现了对象损坏的问题。
C++标准库专门提供了解决这类问题的工具:std::enable_shared_from_this。它能让你从this指针获取到一个和当前管理该对象的shared_ptr共享引用计数的智能指针。具体操作分两步:
- 修改类A的定义,让它继承
std::enable_shared_from_this<A>:
class A : public std::enable_shared_from_this<A> { // 原有的成员和方法... void DoSomething(void); A* Clone(void) const { return new A(*this); } // 注:原代码这里应该是*this(拷贝构造),不是this,可能是笔误 static std::unordered_map<std::shared_ptr<A>,int> myMap{}; };
- 在插入map时,用
shared_from_this()代替this:
if (condition) myMap.insert({shared_from_this(), 5});
shared_from_this()会返回一个std::shared_ptr<A>,它和当前管理该对象的所有shared_ptr(比如你的pA)共享引用计数。这样当pA出作用域时,引用计数只会减1,而myMap里的shared_ptr还持有一个引用,对象就不会被销毁了。
注意:
shared_from_this()只能在已经被shared_ptr管理的对象上调用!也就是说,调用这个方法的对象必须已经被至少一个shared_ptr持有(比如你的场景里,DoSomething是被pA->DoSomething()调用的,pA就是管理当前对象的shared_ptr,所以完全安全)。如果在构造函数里调用shared_from_this(),会导致未定义行为(通常是抛出异常)。
你之前用克隆对象的方法能解决问题,本质是因为你创建了一个全新的对象,插入map的是管理这个新对象的shared_ptr——原pA的销毁只会影响原对象,和新对象无关。但这种方法带来的额外克隆开销,在类A数据复杂的情况下确实不划算,而enable_shared_from_this是零额外开销的标准解决方案。
如果你的递归逻辑需要频繁把当前对象存入全局/静态容器,enable_shared_from_this是最贴合C++标准的设计选择。另外要注意:
- 尽量避免用裸指针构造
shared_ptr,除非你确定这个指针没有被其他shared_ptr管理; - 如果类的对象只会被
shared_ptr管理,可以考虑把构造函数设为私有,只提供返回shared_ptr的工厂方法,从根源避免裸指针的误用。
内容的提问来源于stack exchange,提问作者Aganju

