如何在C++中实现自定义智能指针

自定义智能指针是学习 C++ 内存管理机制的关键一步。它不仅能让你对引用计数、资源归还有更深的理解,还能帮助你在需要特定行为时,创建满足业务需求的指针类型。下面将从设计思路、核心功能、实现细节以及使用示例四个方面,完整阐述如何编写一个简洁且安全的自定义智能指针。

1. 设计思路

  1. 目标:实现一个支持共享所有权的智能指针,类似 std::shared_ptr,但不依赖 STL。
  2. 核心特性
    • 共享计数:多份指针共享同一资源,计数递增/递减。
    • 自动销毁:计数归零时自动释放资源。
    • 线程安全:计数操作需原子化。
    • 简易接口:构造、析构、拷贝、移动、解引用等。
  3. 资源管理:为简化演示,使用 int 或自定义结构体作为托管对象。

2. 核心类结构

class MySharedPtr {
public:
    // 构造函数
    explicit MySharedPtr(T* ptr = nullptr);
    // 拷贝构造
    MySharedPtr(const MySharedPtr& other);
    // 移动构造
    MySharedPtr(MySharedPtr&& other) noexcept;
    // 析构函数
    ~MySharedPtr();

    // 拷贝赋值
    MySharedPtr& operator=(const MySharedPtr& other);
    // 移动赋值
    MySharedPtr& operator=(MySharedPtr&& other) noexcept;

    // 访问操作符
    T& operator*() const;
    T* operator->() const;

    // 计数查询
    long use_count() const noexcept;
    bool unique() const noexcept;
    bool operator==(const MySharedPtr& rhs) const noexcept;
    bool operator!=(const MySharedPtr& rhs) const noexcept;

private:
    void release();          // 计数递减,可能销毁资源
    void add_ref();          // 计数递增

    T* ptr_;                 // 实际托管对象
    std::atomic <long>* ref_; // 原子计数指针
};
  • ptr_ 保存实际托管对象。
  • ref_ 指向原子计数,保证多线程下计数安全。

3. 关键实现细节

#include <atomic>
#include <utility>

template <typename T>
MySharedPtr <T>::MySharedPtr(T* ptr) : ptr_(ptr), ref_(nullptr) {
    if (ptr) {
        ref_ = new std::atomic <long>(1);
    }
}

template <typename T>
MySharedPtr <T>::MySharedPtr(const MySharedPtr& other)
    : ptr_(other.ptr_), ref_(other.ref_) {
    add_ref();
}

template <typename T>
MySharedPtr <T>::MySharedPtr(MySharedPtr&& other) noexcept
    : ptr_(other.ptr_), ref_(other.ref_) {
    other.ptr_ = nullptr;
    other.ref_ = nullptr;
}

template <typename T>
MySharedPtr <T>::~MySharedPtr() {
    release();
}

template <typename T>
void MySharedPtr <T>::add_ref() {
    if (ref_) ref_->fetch_add(1, std::memory_order_relaxed);
}

template <typename T>
void MySharedPtr <T>::release() {
    if (ref_ && ref_->fetch_sub(1, std::memory_order_acq_rel) == 1) {
        delete ptr_;
        delete ref_;
    }
}
  • 构造:若 ptr 非空,分配计数并初始化为 1。
  • 拷贝:共享计数递增。
  • 移动:转移所有权,源对象置空。
  • 析构:计数递减,若为 0 则销毁托管对象和计数。

4. 使用示例

#include <iostream>

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

int main() {
    MySharedPtr <Data> p1(new Data(42));
    std::cout << "use_count: " << p1.use_count() << '\n';

    {
        MySharedPtr <Data> p2 = p1;            // 拷贝
        std::cout << "p2 use_count: " << p2.use_count() << '\n';
        std::cout << "p1->value: " << p1->value << '\n';
    } // p2 离开作用域

    std::cout << "after p2 scope, use_count: " << p1.use_count() << '\n';
    return 0;
}

运行结果示例:

Data(42) constructed
use_count: 1
p2 use_count: 2
p1->value: 42
after p2 scope, use_count: 1
Data(42) destroyed

5. 扩展与注意事项

  • 自定义删除器:可以在构造时接受一个删除器,类似 std::shared_ptrDeleter
  • 弱引用:实现 MyWeakPtrMySharedPtr 配合,避免循环引用。
  • 性能优化:使用 std::shared_ptr 内部的单块内存分配可减少内存碎片。
  • 异常安全:构造时若分配计数失败,需抛出异常;在拷贝/移动过程中使用 try-catch 保证资源不泄漏。

6. 小结

自定义智能指针是理解 C++ 内存管理与 RAII(资源获取即初始化)理念的重要实践。通过上述实现,你可以看到计数器如何确保资源在最后一个指针释放时被正确销毁,以及原子操作如何保证多线程环境下的安全。接下来可以尝试扩展功能:实现 unique_ptr 的移植、弱引用 weak_ptr,或添加线程池等高级特性,以进一步加深对 C++ 内存模型的认识。

发表评论