为何shared_ptr赋值后未将原始指针置空?详解operator=逻辑
问题拆解与解答
1. 为何a2 = a3;后A(2)析构,但原始指针a_raw仍非nullptr且val_为0?
shared_ptr的核心是管理对象的所有权,而非直接控制你手动持有的原始指针。执行a2 = a3;时:
- a2原本持有A(2)的所有权,赋值操作会先递减A(2)的引用计数,当计数降到0时,自动调用A(2)的析构函数销毁对象。
- 你手里的
a_raw是独立于shared_ptr的外部指针——shared_ptr不会主动修改它。对象销毁后,a_raw就成了野指针,此时访问它的val_属于未定义行为,输出0只是内存被回收后出现的随机结果(比如内存被清零或覆盖),完全不可预测。
简言之:shared_ptr只负责自己管控的对象生命周期,不会干预你手动维护的原始指针,对象销毁后原始指针指向的是无效内存,任何访问操作都没有意义。
2. 标准shared_ptr::operator=的核心逻辑
标准库的shared_ptr拷贝赋值运算符遵循以下关键步骤(简化版,实际包含异常安全、线程安全等细节):
- 先增新对象引用计数:对右值
shared_ptr(比如a3)指向的对象,原子递增其引用计数。这一步是为了保证异常安全——如果后续操作抛出异常,新对象的引用计数不会出错,避免对象被错误销毁。 - 再减旧对象引用计数:对当前
shared_ptr(比如a2)原来指向的对象,原子递减其引用计数。如果计数降到0,立即销毁该对象并释放对应内存。 - 更新当前指针与计数指针:将当前
shared_ptr的原始指针替换为右值的原始指针,引用计数指针替换为右值的引用计数指针。 - 自我赋值保护:如果
this == &rhs(即给自己赋值),直接返回*this,避免不必要的计数增减和对象销毁。
这里的「先增后减」是异常安全的关键——如果先减旧计数,后续操作失败的话,旧对象可能已经被销毁,而新对象还没完成接管,会导致资源泄漏或野指针问题。
3. 如何确认自定义shared_ptr拷贝赋值逻辑是否正确?
正确的拷贝赋值逻辑必须满足以下核心要求,你可以对照自己的代码检查:
- 异常安全:必须先递增右值的引用计数,再处理当前对象的旧计数,不能颠倒顺序。
- 自我赋值保护:必须判断是否是给自己赋值,是的话直接返回,避免重复操作。
- 线程安全(可选):如果要支持多线程,引用计数的增减必须是原子操作;单线程场景下用普通整数即可,但也要保证操作的原子性(比如避免指令重排)。
- 资源清理:当旧对象的引用计数降到0时,必须调用对象的析构函数并释放内存。
下面是一个符合要求的简化版实现示例:
template<typename T> class shared_ptr { private: T* ptr_ = nullptr; // 单线程用普通int,多线程用std::atomic<int> int* ref_count_ = nullptr; void increase_ref() { if (ref_count_) { ++(*ref_count_); } } public: // 拷贝赋值运算符 shared_ptr& operator=(const shared_ptr& rhs) { // 自我赋值检查 if (this == &rhs) { return *this; } // 先递增右值的引用计数,保证异常安全 rhs.increase_ref(); // 处理当前对象的旧资源 if (ref_count_) { --(*ref_count_); if (*ref_count_ == 0) { delete ptr_; delete ref_count_; } } // 更新指针和引用计数 ptr_ = rhs.ptr_; ref_count_ = rhs.ref_count_; return *this; } // 其他构造、析构等函数省略... };
内容的提问来源于stack exchange,提问作者mascai
相关产品推荐
相关产品推荐

