自定义C++ shared_ptr程序提前退出:问题排查与修正
问题诊断与修复
你的程序在执行mainres = generateResource();后静默退出,基本是内存访问错误/重复释放资源导致的崩溃,这类问题在手写shared_ptr时非常常见,以下是最可能的原因和修复方案:
常见错误点
- 移动语义实现遗漏关键步骤:移动构造/赋值运算符没有将原对象的资源指针和引用计数指针置为
nullptr。原对象析构时会再次释放已经转移给新对象的资源,直接触发程序崩溃。 - 引用计数管理混乱:
- 拷贝构造/赋值时没有正确递增引用计数,导致多个指针共享资源但计数没同步,析构时提前释放资源。
- 引用计数用裸指针存储,但拷贝时没有指向同一个计数对象,每个
my_shared_ptr各自维护独立计数,最终重复释放资源。 - 引用计数初始值错误(比如设为0而不是1),第一次析构就释放资源,后续指针访问悬空地址。
- 析构函数逻辑错误:引用计数减到0时,只释放了资源指针但没释放计数对象,或者反之,导致内存泄漏或重复释放计数对象。
正确实现示例
下面是一个能正常工作的简化版my_shared_ptr,你可以对照自己的代码修改:
#include <iostream> template<typename T> class my_shared_ptr { private: T* m_ptr = nullptr; size_t* m_ref_count = nullptr; // 释放当前持有的资源 void cleanup() { if (m_ref_count && --(*m_ref_count) == 0) { delete m_ptr; delete m_ref_count; } m_ptr = nullptr; m_ref_count = nullptr; } public: // 默认构造 my_shared_ptr() = default; // 原始指针构造 explicit my_shared_ptr(T* p) : m_ptr(p) { if (m_ptr) { m_ref_count = new size_t(1); } } // 拷贝构造 my_shared_ptr(const my_shared_ptr& other) : m_ptr(other.m_ptr), m_ref_count(other.m_ref_count) { if (m_ref_count) { ++(*m_ref_count); } } // 移动构造:必须把原对象置空 my_shared_ptr(my_shared_ptr&& other) noexcept : m_ptr(other.m_ptr), m_ref_count(other.m_ref_count) { other.m_ptr = nullptr; other.m_ref_count = nullptr; } // 拷贝赋值 my_shared_ptr& operator=(const my_shared_ptr& other) { if (this != &other) { cleanup(); // 先释放当前资源 m_ptr = other.m_ptr; m_ref_count = other.m_ref_count; if (m_ref_count) { ++(*m_ref_count); } } return *this; } // 移动赋值:同样要置空原对象 my_shared_ptr& operator=(my_shared_ptr&& other) noexcept { if (this != &other) { cleanup(); m_ptr = other.m_ptr; m_ref_count = other.m_ref_count; other.m_ptr = nullptr; other.m_ref_count = nullptr; } return *this; } // 析构函数 ~my_shared_ptr() { cleanup(); } // 解引用操作 T& operator*() const { return *m_ptr; } T* operator->() const { return m_ptr; } // 获取当前引用计数 size_t use_count() const { return m_ref_count ? *m_ref_count : 0; } }; // 示例generateResource函数 my_shared_ptr<int> generateResource() { return my_shared_ptr<int>(new int(42)); } int main() { my_shared_ptr<int> mainres; std::cout << "Before calling generateResource" << std::endl; mainres = generateResource(); std::cout << "After generateResource, use count: " << mainres.use_count() << std::endl; std::cout << "Resource value: " << *mainres << std::endl; return 0; }
核心修复说明
- 移动语义必须置空原对象:不管是移动构造还是移动赋值,转移资源后一定要把原对象的指针设为
nullptr,否则原对象析构时会重复释放资源。 - 引用计数要同步:拷贝操作必须递增计数,赋值操作要先释放当前资源再接管新资源,避免内存泄漏或重复释放。
- 边界情况处理:空指针构造、自赋值的判断,确保所有操作都不会访问无效内存。
调试技巧
如果修改后还有问题,可以:
- 在
cleanup()、构造/析构函数里加打印语句,跟踪引用计数的变化和资源释放时机,比如:void cleanup() { std::cout << "Cleanup: current ref count is " << (m_ref_count ? *m_ref_count : 0) << std::endl; // ... 原有逻辑 } - 用GDB或VS调试器断点调试,查看程序崩溃的具体位置,确认是哪一步触发了内存错误。
内容的提问来源于stack exchange,提问作者Invictus
相关产品推荐
相关产品推荐

