**C++中如何实现自定义智能指针并支持多线程安全?**

在 C++ 标准库中,std::shared_ptrstd::unique_ptr 已经非常成熟,但在某些特定场景下我们可能需要一个更轻量或更定制化的智能指针,例如:

  • 只需要弱引用(类似 std::weak_ptr)但不想额外维护引用计数。
  • 对引用计数实现需要自定义内存分配策略。
  • 需要在多线程环境下保证线程安全,但不想使用 std::atomic 过多。

下面给出一种基于原子计数的自定义 ThreadSafeSmartPtr 的实现示例,并说明其线程安全机制、内存管理细节以及如何使用。


1. 设计目标

需求 说明
线程安全 所有对引用计数的增减操作必须原子化。
轻量级 只保留最少必要的成员:指向对象的裸指针、原子引用计数。
自定义析构函数 允许用户在创建指针时提供自定义的销毁策略。
可与 STL 兼容 支持 operator*, operator->, operator bool 等。
无循环引用 仅提供单向引用计数,避免循环引用问题。

2. 基础实现

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

template <typename T>
class ThreadSafeSmartPtr {
public:
    // 构造函数
    explicit ThreadSafeSmartPtr(T* ptr = nullptr,
                                std::function<void(T*)> deleter = nullptr)
        : ptr_(ptr), deleter_(std::move(deleter)), ref_count_(nullptr)
    {
        if (ptr_) {
            // 创建一个新的引用计数
            ref_count_ = new std::atomic <size_t>(1);
        }
    }

    // 拷贝构造
    ThreadSafeSmartPtr(const ThreadSafeSmartPtr& other)
        : ptr_(other.ptr_), deleter_(other.deleter_), ref_count_(other.ref_count_)
    {
        increment();
    }

    // 移动构造
    ThreadSafeSmartPtr(ThreadSafeSmartPtr&& other) noexcept
        : ptr_(other.ptr_), deleter_(std::move(other.deleter_)),
          ref_count_(other.ref_count_)
    {
        other.ptr_ = nullptr;
        other.ref_count_ = nullptr;
    }

    // 赋值运算符
    ThreadSafeSmartPtr& operator=(ThreadSafeSmartPtr other) noexcept {
        swap(other);
        return *this;
    }

    // 析构
    ~ThreadSafeSmartPtr() {
        decrement();
    }

    // 访问
    T& operator*() const { return *ptr_; }
    T* operator->() const { return ptr_; }
    T* get() const noexcept { return ptr_; }
    explicit operator bool() const noexcept { return ptr_ != nullptr; }

    // 用于调试
    size_t use_count() const noexcept {
        return ref_count_ ? ref_count_->load(std::memory_order_relaxed) : 0;
    }

    void reset(T* ptr = nullptr, std::function<void(T*)> deleter = nullptr) {
        ThreadSafeSmartPtr newPtr(ptr, std::move(deleter));
        swap(newPtr);
    }

private:
    void increment() noexcept {
        if (ref_count_) {
            ref_count_->fetch_add(1, std::memory_order_relaxed);
        }
    }

    void decrement() noexcept {
        if (ref_count_ && ref_count_->fetch_sub(1, std::memory_order_acq_rel) == 1) {
            // 计数归零,删除对象
            if (deleter_) deleter_(ptr_);
            else delete ptr_;
            delete ref_count_;
        }
    }

    void swap(ThreadSafeSmartPtr& other) noexcept {
        std::swap(ptr_, other.ptr_);
        std::swap(deleter_, other.deleter_);
        std::swap(ref_count_, other.ref_count_);
    }

    T* ptr_;
    std::function<void(T*)> deleter_;
    std::atomic <size_t>* ref_count_;
};

关键点说明

  1. 引用计数实现

    • 使用 `std::atomic ` 进行计数,保证 `fetch_add` 与 `fetch_sub` 的原子性。
    • memory_order_relaxed 适用于大多数情况;memory_order_acq_reldecrementfetch_sub 里确保析构顺序。
  2. 自定义析构

    • 构造函数接受 std::function<void(T*)>,如果用户未提供则默认使用 delete
    • 这为需要自定义回收策略(如内存池、对象池、网络资源等)提供灵活性。
  3. 拷贝与移动

    • 拷贝构造时先增加计数;移动构造时将指针转移,避免不必要的计数变化。
    • 赋值运算符使用“复制并交换”(copy‑and‑swap)模式,简化异常安全。
  4. 使用场景

    • std::shared_ptr 类似,但更轻量(无 control_block 对象),适合对性能极致要求的高频指针操作。
    • 在多线程环境下,所有计数变更均为原子操作,确保无数据竞争。

3. 示例使用

#include <thread>
#include <vector>

struct Foo {
    int value;
    ~Foo() { std::cout << "Foo destroyed\n"; }
};

int main() {
    ThreadSafeSmartPtr <Foo> p(new Foo{42});
    std::cout << "Initial use_count: " << p.use_count() << '\n';

    // 多线程测试
    std::vector<std::thread> threads;
    for (int i = 0; i < 10; ++i) {
        ThreadSafeSmartPtr <Foo> local = p; // 拷贝,计数加一
        threads.emplace_back([local] {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            std::cout << "Thread use_count: " << local.use_count() << '\n';
        });
    }

    for (auto& t : threads) t.join();

    std::cout << "After threads, use_count: " << p.use_count() << '\n';
    return 0;
}

输出示例(顺序可能略有不同):

Initial use_count: 1
Thread use_count: 11
Thread use_count: 11
...
After threads, use_count: 1
Foo destroyed

4. 进一步扩展

  1. 弱引用
    可以在同一个 ThreadSafeSmartPtr 内部再加一个 std::weak_ptr(或自定义)来实现弱引用,避免循环引用。

  2. 自定义内存分配
    通过在 deleter_ 中使用内存池(如 std::pmr::memory_resource)来实现统一的内存回收。

  3. 可观测性
    use_count() 前后添加日志或事件,以便调试复杂并发程序。

  4. std::allocator 结合
    在构造函数中接受 Allocator 参数,用于分配对象与计数块。


5. 小结

  • 本实现提供了一个 线程安全轻量可自定义析构 的智能指针模板。
  • 它兼容大多数 STL 容器与算法,使用方式类似 std::shared_ptr
  • 通过 std::atomic 确保多线程安全,同时保持了简洁的实现逻辑。
  • 若需更高级特性(如循环引用检测、弱引用、可观测性等),可以在此基础上继续扩展。

发表评论