如何在C++中实现自定义共享指针(Smart Pointer)

在现代 C++ 开发中,智能指针是管理动态内存的重要工具。标准库中已经提供了 std::shared_ptrstd::unique_ptr 等实现,但在一些特定场景下,你可能需要一个更轻量或更具可定制性的共享指针。下面将展示一个最小化、易于理解的自定义 SharedPtr 实现,并讨论其在多线程环境中的使用注意事项。

1. 设计思路

  • 引用计数:核心是一个共享计数器,记录指向同一资源的 SharedPtr 对象数量。
  • 原子操作:为了线程安全,计数器使用 `std::atomic `。
  • 自定义删除器:支持用户传入自定义的析构函数,以兼容多态或非 new 分配的资源。
  • 基本接口:实现 operator*, operator->, get(), use_count(), reset(), swap() 等常用成员。

2. 代码实现

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

template<typename T>
class SharedPtr {
public:
    // 默认构造,指向空
    SharedPtr() noexcept : ptr_(nullptr), count_(nullptr), deleter_(nullptr) {}

    // 从裸指针创建
    explicit SharedPtr(T* p, std::function<void(T*)> deleter = std::default_delete<T>())
        : ptr_(p), deleter_(std::move(deleter)) {
        count_ = new std::atomic <size_t>(1);
    }

    // 拷贝构造
    SharedPtr(const SharedPtr& other) noexcept
        : ptr_(other.ptr_), count_(other.count_), deleter_(other.deleter_) {
        if (count_) ++(*count_);
    }

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

    // 拷贝赋值
    SharedPtr& operator=(const SharedPtr& other) {
        if (this != &other) {
            release();                       // 先释放自身资源
            ptr_ = other.ptr_;
            count_ = other.count_;
            deleter_ = other.deleter_;
            if (count_) ++(*count_);
        }
        return *this;
    }

    // 移动赋值
    SharedPtr& operator=(SharedPtr&& other) noexcept {
        if (this != &other) {
            release();
            ptr_ = other.ptr_;
            count_ = other.count_;
            deleter_ = std::move(other.deleter_);
            other.ptr_ = nullptr;
            other.count_ = nullptr;
        }
        return *this;
    }

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

    // 解引用
    T& operator*() const { assert(ptr_); return *ptr_; }
    T* operator->() const { assert(ptr_); return ptr_; }
    T* get() const noexcept { return ptr_; }

    size_t use_count() const noexcept { return count_ ? *count_ : 0; }

    void reset() noexcept {
        release();
        ptr_ = nullptr;
        count_ = nullptr;
        deleter_ = nullptr;
    }

    void reset(T* p) {
        if (ptr_ != p) {
            release();
            ptr_ = p;
            deleter_ = std::default_delete <T>();
            count_ = new std::atomic <size_t>(1);
        }
    }

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

    explicit operator bool() const noexcept { return ptr_ != nullptr; }

private:
    void release() {
        if (count_ && --(*count_) == 0) {
            if (deleter_) deleter_(ptr_);
            delete count_;
        }
    }

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

3. 使用示例

struct Base { virtual void foo() { std::cout << "Base\n"; } };
struct Derived : Base { void foo() override { std::cout << "Derived\n"; } };

int main() {
    SharedPtr <Base> sp1(new Derived());           // 计数为1
    {
        SharedPtr <Base> sp2 = sp1;                // 计数变为2
        std::cout << sp2.use_count() << "\n";     // 输出2
        sp2->foo();                               // 调用 Derived::foo
    } // sp2析构,计数变为1
    std::cout << sp1.use_count() << "\n";         // 输出1

    // 自定义删除器(例如使用 `malloc` 分配)
    SharedPtr <int> sp3(reinterpret_cast<int*>(std::malloc(sizeof(int))),
                       [](int* p){ std::free(p); });
    *sp3 = 42;
    std::cout << *sp3 << "\n";                    // 输出42

    return 0;
}

4. 多线程注意事项

  • 计数器使用 std::atomic,实现了基本的线程安全计数增减。
  • 对同一对象的并发访问(读写)仍需外部同步(如 std::mutex),因为 SharedPtr 本身不提供数据层面的同步。
  • reset()release() 的实现是原子操作,但在高并发场景下,频繁的 reset() 可能导致大量内存分配/释放。可考虑预先分配计数器或使用内存池。

5. 与 std::shared_ptr 的比较

特性 std::shared_ptr 自定义 SharedPtr
线程安全计数
兼容性 与 STL 容器、算法完美配合 需要自行实现容器友好
性能 内置优化(如 make_shared 可能更轻量,但缺少优化
删除器 支持自定义 支持自定义
其他 友好异常安全、weak_ptr 支持 仅实现核心功能

6. 结语

自定义共享指针可以帮助你在特殊需求下精细控制内存管理、减少不必要的开销或满足教学需求。但在大多数生产环境中,建议优先使用标准库提供的 std::shared_ptr。如果你确实需要定制行为,以上实现已经具备基本的可用性,后续可以根据项目需求进一步扩展。

发表评论