如何在 C++ 中实现自定义线程安全的智能指针

在 C++ 中,智能指针是管理资源生命周期的重要工具。标准库已经提供了 std::unique_ptrstd::shared_ptr 等实现,但在某些场景下,可能需要自己实现一个支持多线程安全的引用计数智能指针。下面将从设计思路、核心实现到使用示例,逐步介绍如何编写一个简易但线程安全的 MySharedPtr


1. 设计目标

功能 说明
引用计数 使用 std::atomic<std::size_t> 维护计数,避免锁
线程安全 对计数的增减均为原子操作,构造/析构时无数据竞争
自定义删除器 支持传入删除器,用于自定义资源释放方式
可复制/可移动 复制时计数递增,移动后源对象失效
弱引用 提供 MyWeakPtr,支持 lock() 获取有效指针

2. 基础实现

#include <atomic>
#include <utility>
#include <iostream>

template <typename T>
class MySharedPtr;

template <typename T>
class MyWeakPtr;

template <typename T>
class ControlBlock {
public:
    std::atomic<std::size_t> use_count{1};
    std::atomic<std::size_t> weak_count{0};
    T* ptr;
    void (*deleter)(T*) = nullptr;

    ControlBlock(T* p, void (*del)(T*) = nullptr)
        : ptr(p), deleter(del) {}

    void release() {
        if (ptr && deleter) deleter(ptr);
        delete ptr;
    }
};

template <typename T>
class MySharedPtr {
public:
    MySharedPtr() noexcept : cb(nullptr) {}

    explicit MySharedPtr(T* ptr, void (*del)(T*) = nullptr)
        : cb(ptr ? new ControlBlock <T>(ptr, del) : nullptr) {}

    MySharedPtr(const MySharedPtr& other) noexcept
        : cb(other.cb) {
        increment();
    }

    MySharedPtr(MySharedPtr&& other) noexcept
        : cb(other.cb) {
        other.cb = nullptr;
    }

    ~MySharedPtr() {
        decrement();
    }

    MySharedPtr& operator=(const MySharedPtr& other) noexcept {
        if (this != &other) {
            decrement();
            cb = other.cb;
            increment();
        }
        return *this;
    }

    MySharedPtr& operator=(MySharedPtr&& other) noexcept {
        if (this != &other) {
            decrement();
            cb = other.cb;
            other.cb = nullptr;
        }
        return *this;
    }

    T* get() const noexcept { return cb ? cb->ptr : nullptr; }
    T& operator*() const noexcept { return *get(); }
    T* operator->() const noexcept { return get(); }
    std::size_t use_count() const noexcept {
        return cb ? cb->use_count.load(std::memory_order_acquire) : 0;
    }
    bool unique() const noexcept { return use_count() == 1; }

    // For weak pointer creation
    friend class MyWeakPtr <T>;

private:
    ControlBlock <T>* cb;

    void increment() {
        if (cb) cb->use_count.fetch_add(1, std::memory_order_acq_rel);
    }

    void decrement() {
        if (cb) {
            if (cb->use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
                cb->release();
                if (cb->weak_count.load(std::memory_order_acquire) == 0) {
                    delete cb;
                }
            }
        }
    }
};

template <typename T>
class MyWeakPtr {
public:
    MyWeakPtr() noexcept : cb(nullptr) {}

    MyWeakPtr(const MySharedPtr <T>& shared) noexcept
        : cb(shared.cb) {
        increment();
    }

    MyWeakPtr(const MyWeakPtr& other) noexcept
        : cb(other.cb) {
        increment();
    }

    MyWeakPtr(MyWeakPtr&& other) noexcept
        : cb(other.cb) {
        other.cb = nullptr;
    }

    ~MyWeakPtr() {
        decrement();
    }

    MyWeakPtr& operator=(const MyWeakPtr& other) noexcept {
        if (this != &other) {
            decrement();
            cb = other.cb;
            increment();
        }
        return *this;
    }

    MyWeakPtr& operator=(MyWeakPtr&& other) noexcept {
        if (this != &other) {
            decrement();
            cb = other.cb;
            other.cb = nullptr;
        }
        return *this;
    }

    MySharedPtr <T> lock() const noexcept {
        if (!cb) return MySharedPtr <T>();
        std::size_t curr = cb->use_count.load(std::memory_order_acquire);
        while (curr != 0) {
            if (cb->use_count.compare_exchange_weak(
                    curr, curr + 1,
                    std::memory_order_acq_rel,
                    std::memory_order_acquire)) {
                return MySharedPtr <T>(cb);
            }
        }
        return MySharedPtr <T>();
    }

    bool expired() const noexcept {
        return !cb || cb->use_count.load(std::memory_order_acquire) == 0;
    }

    std::size_t use_count() const noexcept {
        return cb ? cb->use_count.load(std::memory_order_acquire) : 0;
    }

private:
    ControlBlock <T>* cb;

    void increment() {
        if (cb) cb->weak_count.fetch_add(1, std::memory_order_acq_rel);
    }

    void decrement() {
        if (cb) {
            if (cb->weak_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
                if (cb->use_count.load(std::memory_order_acquire) == 0) {
                    delete cb;
                }
            }
        }
    }
};

关键点说明

  1. 原子引用计数
    std::atomic<std::size_t> 保证了在多线程环境下计数的正确性,无需加锁。

  2. 删除器
    默认使用 delete,但用户可以传入自定义删除器,适用于 malloc/freenew[]/delete[] 或自定义资源。

  3. 弱指针实现
    MyWeakPtr 与标准库实现保持一致,支持 lock() 获取强引用,并且在引用计数归零后才释放控制块。


3. 使用示例

#include <thread>
#include <vector>

struct Resource {
    int id;
    Resource(int x) : id(x) { std::cout << "Resource " << id << " constructed\n"; }
    ~Resource() { std::cout << "Resource " << id << " destructed\n"; }
};

int main() {
    MySharedPtr <Resource> sp1(new Resource(42));
    std::cout << "use_count: " << sp1.use_count() << '\n'; // 1

    {
        MySharedPtr <Resource> sp2 = sp1;
        std::cout << "After copy, use_count: " << sp1.use_count() << '\n'; // 2

        MyWeakPtr <Resource> wp = sp1;
        std::cout << "Weak use_count: " << wp.use_count() << '\n'; // 2

        // 多线程访问
        std::vector<std::thread> threads;
        for (int i = 0; i < 5; ++i) {
            threads.emplace_back([sp1]() {
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
                std::cout << "Thread got id: " << sp1->id << '\n';
            });
        }
        for (auto& t : threads) t.join();
    }

    std::cout << "After block, use_count: " << sp1.use_count() << '\n'; // 1
    // 当 sp1 失效时,Resource 会被析构
    return 0;
}

运行结果示例:

Resource 42 constructed
use_count: 1
After copy, use_count: 2
Weak use_count: 2
Thread got id: 42
Thread got id: 42
Thread got id: 42
Thread got id: 42
Thread got id: 42
After block, use_count: 1
Resource 42 destructed

4. 进一步优化

优化方向 说明
内存池 ControlBlock 与对象共置,减少分配次数
锁优化 对于读多写少场景,可使用 std::shared_mutex 与读写锁
自定义内存分配器 通过模板参数注入分配器,兼容多种内存管理策略
异常安全 在构造时若分配失败抛异常,确保没有泄漏

小结

通过上述实现,我们获得了一个轻量、线程安全、支持自定义删除器的智能指针。虽然标准库已经提供了 std::shared_ptrstd::weak_ptr,但在需要对内部细节做特殊控制或嵌入自定义资源管理时,自定义实现可以提供更大的灵活性。希望本文能帮助你更深入地理解智能指针的内部工作原理,并为自己的项目提供参考。

发表评论