You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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;
}

核心修复说明

  1. 移动语义必须置空原对象:不管是移动构造还是移动赋值,转移资源后一定要把原对象的指针设为nullptr,否则原对象析构时会重复释放资源。
  2. 引用计数要同步:拷贝操作必须递增计数,赋值操作要先释放当前资源再接管新资源,避免内存泄漏或重复释放。
  3. 边界情况处理:空指针构造、自赋值的判断,确保所有操作都不会访问无效内存。

调试技巧

如果修改后还有问题,可以:

  • 在cleanup()、构造/析构函数里加打印语句,跟踪引用计数的变化和资源释放时机,比如:
    void cleanup() {
        std::cout << "Cleanup: current ref count is " << (m_ref_count ? *m_ref_count : 0) << std::endl;
        // ... 原有逻辑
    }
    
  • 用GDB或VS调试器断点调试,查看程序崩溃的具体位置,确认是哪一步触发了内存错误。

内容的提问来源于stack exchange,提问作者Invictus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 14:42:47