自定义 C++ 智能指针的设计与实现

在现代 C++ 开发中,std::unique_ptrstd::shared_ptrstd::weak_ptr 已经为我们提供了强大的资源管理能力。但在某些特殊场景下,标准库的实现并不完全满足需求,例如:

  1. 需要对资源释放过程做更细粒度的控制;
  2. 需要在指针生命周期中插入自定义的日志、计数或安全检查;
  3. 需要兼容旧有代码或第三方库的接口。

下面以一个“自定义共享指针(MySharedPtr)”为例,展示如何从零实现一个可替代 std::shared_ptr 的简易版本,并说明其核心实现思路。

1. 设计目标

  • 引用计数:采用原子计数,支持多线程安全。
  • 自定义释放策略:允许用户传入自定义 Deleter
  • 内联存储:在小对象场景下使用“对象分配器”把指针和计数放在同一次内存分配中,减少分配次数。
  • 兼容性:提供 operator*operator->get()use_count() 等接口。

2. 核心实现细节

template <typename T, typename Deleter = std::default_delete<T>>
class MySharedPtr {
public:
    // 构造
    explicit MySharedPtr(T* ptr = nullptr, Deleter del = Deleter())
        : control_(nullptr), ptr_(ptr), deleter_(del) {
        if (ptr_) {
            control_ = new ControlBlock(ptr_, deleter_);
        }
    }

    // 拷贝构造
    MySharedPtr(const MySharedPtr& other)
        : control_(other.control_), ptr_(other.ptr_), deleter_(other.deleter_) {
        if (control_) control_->add_ref();
    }

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

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

    // 赋值
    MySharedPtr& operator=(MySharedPtr other) noexcept {
        swap(other);
        return *this;
    }

    // 交换
    void swap(MySharedPtr& other) noexcept {
        std::swap(control_, other.control_);
        std::swap(ptr_, other.ptr_);
        std::swap(deleter_, other.deleter_);
    }

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

private:
    struct ControlBlock {
        std::atomic <size_t> count;
        Deleter del;
        T* ptr;
        ControlBlock(T* p, Deleter d) : count(1), del(d), ptr(p) {}
        void add_ref() { count.fetch_add(1, std::memory_order_relaxed); }
        void release() {
            if (count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
                del(ptr);
                delete this;
            }
        }
    };

    void release() {
        if (control_) {
            control_->release();
            control_ = nullptr;
            ptr_ = nullptr;
        }
    }

    ControlBlock* control_;
    T* ptr_;
    Deleter deleter_;
};

关键点说明

  1. 控制块(ControlBlock

    • 存储引用计数、删除器和原始指针。
    • 采用 std::atomic 实现线程安全。
    • release() 在计数归零时调用删除器并释放控制块自身。
  2. 自定义删除器

    • 模板参数 Deleter 默认是 `std::default_delete `。
    • 通过 MySharedPtr(ptr, deleter) 可以传入任何可调用对象,例如 lambda、函数指针或自定义类。
  3. 内联分配

    • 若需要在控制块与对象共存,改用单次 operator new 分配一块内存,将对象和计数包装在同一块中。
    • 这里为演示简化,直接分配两块内存。

3. 使用示例

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

int main() {
    // 使用默认删除器
    MySharedPtr <MyStruct> sp1(new MyStruct{42});
    std::cout << "use_count: " << sp1.use_count() << '\n'; // 1

    {
        MySharedPtr <MyStruct> sp2 = sp1; // 拷贝
        std::cout << "use_count: " << sp1.use_count() << '\n'; // 2
    } // sp2 离开作用域

    std::cout << "use_count: " << sp1.use_count() << '\n'; // 1

    // 使用自定义删除器
    auto deleter = [](MyStruct* p){ 
        std::cout << "Custom delete\n";
        delete p; 
    };
    MySharedPtr <MyStruct> sp3(new MyStruct{99}, deleter);
    // sp3 离开时会调用 deleter
}

运行结果示例:

use_count: 1
use_count: 2
use_count: 1
MyStruct destroyed
Custom delete

4. 性能与扩展

  • 性能:相较于 std::shared_ptr,上述实现缺少内联计数、分配优化等,适合作为教学示例。
  • 扩展
    • 加入 weak_ptr 版本:MyWeakPtr,管理弱引用。
    • 对象池化:将 ControlBlock 放入自定义池中。
    • 支持数组:专门的 MyArrayPtr,兼容 delete[]

5. 小结

通过上述实现,我们可以看到自定义智能指针的核心机制:引用计数、删除器和资源解放。
在实际项目中,如果现有智能指针无法满足特殊需求,完全可以基于此思路自行扩展;否则建议直接使用 STL 提供的 std::shared_ptrstd::unique_ptr 等标准实现,以获得更完善的错误检查、异常安全和性能优化。

发表评论