**C++ 中的智能指针:自己实现一个 `shared_ptr` 的思路与关键点**

在 C++11 之前,管理动态分配的资源主要靠手写的 delete,这很容易导致内存泄漏、野指针等问题。C++11 开始引入了标准智能指针 std::unique_ptrstd::shared_ptrstd::weak_ptr,极大简化了资源管理。下面我们以 std::shared_ptr 为例,探讨自己实现一个共享指针时需要关注的核心设计与实现细节,帮助读者深入理解其工作机制。


1. 共享计数的基本思路

std::shared_ptr 的核心是引用计数。每个共享指针实例都持有一份对同一对象的引用计数,只有当计数降到 0 时才真正释放对象。实现共享计数通常采用一个独立的计数器对象(如 std::atomic<std::size_t>),或者直接将计数器放在一个控制块(control block)里。

template <typename T>
class SharedPtr {
private:
    T* ptr;                     // 实际指向的对象
    std::size_t* refCount;      // 引用计数
    // ...
};

关键在于 计数的原子性:多线程环境下,计数器的加减操作必须是线程安全的。常见做法是使用 std::atomic<std::size_t> 或者在每个 SharedPtr 的复制/移动操作时手动锁住计数器。


2. 构造与析构

2.1 默认构造

默认构造不指向任何对象,计数器为 nullptr

SharedPtr() : ptr(nullptr), refCount(nullptr) {}

2.2 从裸指针构造

直接使用裸指针时,需要为计数器分配空间,并初始化为 1。

explicit SharedPtr(T* p) : ptr(p) {
    refCount = new std::size_t(1);
}

2.3 拷贝构造

拷贝构造时,需要把指针和计数器复制过来,并对计数器递增。

SharedPtr(const SharedPtr& other) : ptr(other.ptr), refCount(other.refCount) {
    if (refCount) ++(*refCount);
}

2.4 移动构造

移动构造时,将资源所有权转移给新对象,源对象置为空。

SharedPtr(SharedPtr&& other) noexcept : ptr(other.ptr), refCount(other.refCount) {
    other.ptr = nullptr;
    other.refCount = nullptr;
}

2.5 析构

析构时递减计数器,并在计数为 0 时删除指针和计数器。

~SharedPtr() {
    release();
}
void release() {
    if (refCount && --(*refCount) == 0) {
        delete ptr;
        delete refCount;
    }
}

3. 赋值操作

3.1 拷贝赋值

先递减自身计数,再复制别人的指针与计数器,最后递增新计数器。

SharedPtr& operator=(const SharedPtr& other) {
    if (this != &other) {
        release();            // 先释放旧资源
        ptr = other.ptr;
        refCount = other.refCount;
        if (refCount) ++(*refCount);
    }
    return *this;
}

3.2 移动赋值

先释放旧资源,然后转移指针和计数器。

SharedPtr& operator=(SharedPtr&& other) noexcept {
    if (this != &other) {
        release();
        ptr = other.ptr;
        refCount = other.refCount;
        other.ptr = nullptr;
        other.refCount = nullptr;
    }
    return *this;
}

4. 访问与操作

  • operator*operator->:提供对所管理对象的访问。
  • use_count():返回当前引用计数(如果计数器为空返回 0)。
  • unique():当计数为 1 时返回 true。
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }

std::size_t use_count() const { return refCount ? *refCount : 0; }
bool unique() const { return use_count() == 1; }

5. 线程安全细节

如果你想让 SharedPtr 在多线程中安全使用,最简单的做法是把 refCount 定义为 std::atomic<std::size_t>

std::atomic<std::size_t>* refCount;

然后所有的加/减计数操作都使用 ++(*refCount)--(*refCount)
注意:--(*refCount) 的返回值不一定是新的计数,需要先递减后判断是否为 0。


6. 控制块(Control Block)改进

上面示例使用了两个独立的动态分配对象(ptrrefCount)。实际实现中,C++ 标准库通常采用一个 控制块ControlBlock)来同时存储指针、计数、以及可选的自定义删除器。

struct ControlBlock {
    T* ptr;
    std::atomic<std::size_t> count;
    // 可选自定义删除器
    std::function<void(T*)> deleter;
};

SharedPtr 只持有指向 ControlBlock 的指针。这样可以在需要时支持 自定义删除器弱引用weak_ptr)等高级功能。


7. 完整代码(简化版)

#include <atomic>
#include <cstddef>
#include <functional>

template <typename T>
class SharedPtr {
private:
    struct ControlBlock {
        T* ptr;
        std::atomic<std::size_t> count;
        std::function<void(T*)> deleter;

        ControlBlock(T* p)
            : ptr(p), count(1), deleter([](T* p){ delete p; }) {}
    };

    ControlBlock* cb;

    void release() {
        if (cb && --cb->count == 0) {
            cb->deleter(cb->ptr);
            delete cb;
        }
    }

public:
    // 默认构造
    SharedPtr() : cb(nullptr) {}

    // 从裸指针构造
    explicit SharedPtr(T* p) : cb(new ControlBlock(p)) {}

    // 拷贝构造
    SharedPtr(const SharedPtr& other) : cb(other.cb) {
        if (cb) ++cb->count;
    }

    // 移动构造
    SharedPtr(SharedPtr&& other) noexcept : cb(other.cb) {
        other.cb = nullptr;
    }

    // 析构
    ~SharedPtr() { release(); }

    // 拷贝赋值
    SharedPtr& operator=(const SharedPtr& other) {
        if (this != &other) {
            release();
            cb = other.cb;
            if (cb) ++cb->count;
        }
        return *this;
    }

    // 移动赋值
    SharedPtr& operator=(SharedPtr&& other) noexcept {
        if (this != &other) {
            release();
            cb = other.cb;
            other.cb = nullptr;
        }
        return *this;
    }

    // 访问
    T& operator*() const { return *(cb->ptr); }
    T* operator->() const { return cb->ptr; }

    // 信息
    std::size_t use_count() const { return cb ? cb->count : 0; }
    bool unique() const { return use_count() == 1; }
};

8. 小结

  • 引用计数是实现 shared_ptr 的核心,需保证线程安全。
  • 控制块是实现自定义删除器、弱引用的关键结构。
  • 拷贝/移动语义需仔细处理计数递增/递减和资源转移。
  • 通过上述实现,可以更好地理解标准库 std::shared_ptr 的工作机制,为后续学习 std::weak_ptrstd::enable_shared_from_this 等高级特性打下坚实基础。

希望这篇文章能帮助你从底层实现角度把握共享指针的设计与实现,为日后的 C++ 代码写作提供更深的技术支撑。

发表评论