返回题库

shared_ptr 引用计数机制

Shared Ptr Reference Count

专题
General / 综合
难度
L1
来源
MyntBit

题目详情

高效内存管理在量化交易系统中至关重要,用以避免不可预测的垃圾回收暂停和内存泄漏。虽然标准库提供线程安全的引用计数智能指针,但理解其底层实现是量化金融领域高级 C++ 开发者的基本要求。

任务:设计一个简化版的 std::shared_ptr,展示引用计数和控制块管理的核心机制。

英文原题

Efficient memory management is critical in quantitative trading systems to avoid unpredictable garbage collection pauses and memory leaks. While standard libraries provide thread-safe reference-counted smart pointers, understanding their underlying implementation is a fundamental requirement for C++ developers optimizing high-frequency trading applications.
Task
Implement a simplified, non-thread-safe SharedPtr<T> template class to manage a dynamically allocated object and its reference count.

解析

问题分析

`std::shared_ptr` 是 C++ 中最常用的智能指针之一,但其引用计数机制在量化交易系统中需要深入理解。控制块(control block)的分配、线程安全保证、以及 `make_shared` 的内存合并优化都直接影响延迟和内存效率。

核心机制

// 简化版 shared_ptr 实现
template<typename T>
class SimpleSharedPtr {
    T* ptr_ = nullptr;
    struct ControlBlock { std::atomic<long> refs{1}; std::atomic<long> weaks{1}; };
    ControlBlock* cb_ = nullptr;
public:
    template<typename... Args>
    static SimpleSharedPtr make(Args&&... args) {
        auto* mem = ::operator new(sizeof(T) + sizeof(ControlBlock));
        auto* ptr = new (mem) T(std::forward<Args>(args)...);
        auto* cb = new (static_cast<char*>(mem)+sizeof(T)) ControlBlock;
        return SimpleSharedPtr(ptr, cb);
    }
    
    SimpleSharedPtr(const SimpleSharedPtr& other) : ptr_(other.ptr_), cb_(other.cb_) {
        if (cb_) cb_->refs++;
    }
    
    ~SimpleSharedPtr() {
        if (cb_ && --cb_->refs == 0) {
            ptr_->~T();
            if (--cb_->weaks == 0) ::operator delete(ptr_);
        }
    }
};

关键考虑

  1. make_shared 优化:将 T 和控制块合并为一次内存分配,减少分配次数并提升缓存局部性。
  2. 原子操作成本:`std::atomic` 的递增/递减在 x86 上使用 LOCK 前缀,约 20-30 个 CPU 周期。在热路径上应避免不必要的拷贝。
  3. 控制块分离:从裸指针构造 `shared_ptr` 会创建独立控制块,导致双重删除。始终从 `make_shared` 或已有的 `shared_ptr` 拷贝。
  4. weak_ptr 影响:`weak_ptr` 的存在会阻止控制块释放(即使对象已析构),需要评估内存占用。
  5. 时间复杂度:拷贝/析构 O(1);空间开销:每个 shared_ptr 16 字节(64位系统)+ 控制块 16 字节。

英文解析

Analysis

`std::shared_ptr` is one of the most commonly used smart pointers in C++, but its reference counting mechanism requires deep understanding in quantitative trading systems. Control block allocation, thread safety guarantees, and the `make_shared` memory coalescing optimization all directly impact latency and memory efficiency.

Solution

// Simplified shared_ptr implementation
template<typename T>
class SimpleSharedPtr {
    T* ptr_ = nullptr;
    struct ControlBlock { std::atomic<long> refs{1}; std::atomic<long> weaks{1}; };
    ControlBlock* cb_ = nullptr;
public:
    template<typename... Args>
    static SimpleSharedPtr make(Args&&... args) {
        auto* mem = ::operator new(sizeof(T) + sizeof(ControlBlock));
        auto* ptr = new (mem) T(std::forward<Args>(args)...);
        auto* cb = new (static_cast<char*>(mem)+sizeof(T)) ControlBlock;
        return SimpleSharedPtr(ptr, cb);
    }
    
    SimpleSharedPtr(const SimpleSharedPtr& other) : ptr_(other.ptr_), cb_(other.cb_) {
        if (cb_) cb_->refs++;
    }
    
    ~SimpleSharedPtr() {
        if (cb_ && --cb_->refs == 0) {
            ptr_->~T();
            if (--cb_->weaks == 0) ::operator delete(ptr_);
        }
    }
};

Complexity & Edge Cases

  • Time complexity: O(1) for copy/destroy with atomic control block; O(1) for dereference
  • Space complexity: O(1) per pointer plus O(1) for control block overhead
  • Edge cases: (1) Circular references prevent deallocation — use weak_ptr to break cycles. (2) Control block must survive beyond last shared_ptr copy. (3) Custom deleter must handle null pointers.

Key Considerations

  1. make_shared optimization: Coalesces T and the control block into a single allocation, reducing allocation count and improving cache locality.
  2. Atomic operation cost: `std::atomic` increment/decrement uses the LOCK prefix on x86, costing ~20-30 CPU cycles. Avoid unnecessary copies on hot paths.
  3. Control block separation: Constructing `shared_ptr` from a raw pointer creates an independent control block, risking double deletion. Always construct from `make_shared` or copy an existing `shared_ptr`.
  4. weak_ptr impact: `weak_ptr` existence prevents control block release (even after object destruction), requiring memory occupancy evaluation.
  5. Time complexity: copy/destroy O(1); Space overhead: each shared_ptr 16 bytes (64-bit) + control block 16 bytes.