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

如何证明C++11中make_shared的shared_ptr控制块需等weak_ptr计数归零才销毁?

关于shared_ptr与weak_ptr引用计数及控制块存活的验证问题

核心问题

是否存在shared_ptr引用计数为0,但weak_ptr引用计数不为0的情况?

理论依据

当shared_ptr由make_shared创建且存在指向它的weak_ptr时,其控制块会一直存活,直到shared_ptr和weak_ptr的引用计数均归0。原文说明:

There must be a way for weak_ptrs to determine if the managed object is still valid (eg. for lock). They do this by checking the number of shared_ptrs that own the managed object, which is stored in the control block. The result is that the control blocks are alive until the shared_ptr count and the weak_ptr count both hit 0.

原测试代码的局限性

我曾尝试通过以下代码验证该结论,但两个use_count()均输出0,无法判断控制块是否存活:

#include<memory>
#include<iostream>
using namespace std;
struct My {
    int m_i;
    My(int i) : m_i(i) { cout << "My ctor:" << m_i << '\n';}
    ~My() { cout << "My dtor:" << m_i << '\n';} // 修正原代码的笔误:析构函数输出应为dtor
};
weak_ptr<My> wp1, wp2;
int main() {
    {
        auto sp1 = shared_ptr<My>(new My(30));
        wp1 = weak_ptr<My>(sp1);
    }
    cout<< wp1.use_count() << endl;
    {
        auto sp2 = make_shared<My>(40);
        wp2 = weak_ptr<My>(sp2);
    }
    cout<< wp2.use_count() << endl;
    return 0;
}

注:use_count()返回的是shared_ptr的共享计数,无法直接反映weak_ptr的弱计数或控制块的存活状态。


验证控制块存活的有效方法

方法一:自定义分配器跟踪内存生命周期

通过自定义分配器,可以直观看到make_shared的内存分配特性——它会一次性分配托管对象+控制块的内存,只有当weak_ptr也销毁时,这块内存才会被释放。

#include <memory>
#include <iostream>
#include <new>

// 自定义分配器,跟踪内存的分配与释放
template<typename T>
struct TrackAllocator {
    using value_type = T;

    TrackAllocator() = default;
    template<typename U>
    TrackAllocator(const TrackAllocator<U>&) noexcept {}

    T* allocate(std::size_t n) {
        T* ptr = static_cast<T*>(std::malloc(n * sizeof(T)));
        std::cout << "分配内存:" << n * sizeof(T) << "字节 @ " << ptr << '\n';
        return ptr;
    }

    void deallocate(T* ptr, std::size_t n) noexcept {
        std::cout << "释放内存:" << n * sizeof(T) << "字节 @ " << ptr << '\n';
        std::free(ptr);
    }
};

template<typename T, typename U>
bool operator==(const TrackAllocator<T>&, const TrackAllocator<U>&) noexcept { return true; }
template<typename T, typename U>
bool operator!=(const TrackAllocator<T>&, const TrackAllocator<U>&) noexcept { return false; }

struct My {
    int m_i;
    My(int i) : m_i(i) { std::cout << "My构造:" << m_i << '\n'; }
    ~My() { std::cout << "My析构:" << m_i << '\n'; }
};

int main() {
    std::cout << "=== 用new创建shared_ptr ===" << '\n';
    {
        std::weak_ptr<My> wp;
        {
            std::shared_ptr<My> sp(new My(30));
            wp = sp;
            std::cout << "shared_ptr计数:" << sp.use_count() << '\n';
        }
        std::cout << "weak_ptr是否过期:" << std::boolalpha << wp.expired() << '\n';
        // 此时对象和控制块已分别释放
    }

    std::cout << "\n=== 用make_shared创建shared_ptr ===" << '\n';
    {
        std::weak_ptr<My> wp;
        {
            auto sp = std::allocate_shared<My>(TrackAllocator<My>(), 40);
            wp = sp;
            std::cout << "shared_ptr计数:" << sp.use_count() << '\n';
        }
        std::cout << "weak_ptr是否过期:" << std::boolalpha << wp.expired() << '\n';
        // 此时对象已析构,但包含控制块的内存还未释放
    }
    std::cout << "\n=== 离开weak_ptr作用域 ===" << '\n';
    // 此时weak_ptr销毁,控制块所在内存才会被释放
    return 0;
}

运行结果解析:

  • new创建的shared_ptr:shared_ptr销毁后,立即触发对象析构和控制块内存释放。
  • make_shared创建的shared_ptr:shared_ptr销毁后仅触发对象析构,直到weak_ptr销毁时,才会释放包含控制块的整块内存。

方法二:模拟控制块的双计数逻辑

shared_ptr的控制块本质包含两个独立计数:

  • 共享计数:记录shared_ptr的数量,归0时销毁托管对象。
  • 弱计数:记录weak_ptr的数量,归0时销毁控制块。

以下是简化的模拟代码:

#include <iostream>
#include <atomic>

struct ControlBlock {
    std::atomic<int> shared_count = 1;
    std::atomic<int> weak_count = 0;

    // 当shared_ptr销毁时调用
    void decrement_shared() {
        if (--shared_count == 0) {
            std::cout << "销毁托管对象\n";
            // 若弱计数也为0,销毁控制块
            if (weak_count == 0) {
                std::cout << "销毁控制块\n";
                delete this;
            }
        }
    }

    // 当weak_ptr销毁时调用
    void decrement_weak() {
        if (--weak_count == 0) {
            // 若共享计数已为0,销毁控制块
            if (shared_count == 0) {
                std::cout << "销毁控制块\n";
                delete this;
            }
        }
    }
};

当shared_ptr销毁后共享计数归0,只要还有weak_ptr存在(弱计数>0),控制块就会继续存活;只有当所有weak_ptr也销毁,弱计数归0时,控制块才会被销毁。


结论

确实存在shared_ptr引用计数(共享计数)为0,但weak_ptr引用计数(弱计数)不为0的情况:

  • 若shared_ptr由make_shared创建,且存在关联的weak_ptr,shared_ptr销毁后共享计数归0,托管对象被销毁,但控制块因弱计数大于0仍存活,直到所有weak_ptr也被销毁。
  • 若shared_ptr通过new创建,控制块和对象内存分开分配,shared_ptr销毁后对象和控制块会立即被销毁(此时weak_ptr变为无效状态)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 16:54:53