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

在现代 C++ 开发中,std::shared_ptrstd::unique_ptr 等智能指针已经非常成熟且广泛使用。然而,在某些特定场景下,开发者可能需要更细粒度的控制,例如实现线程安全的引用计数、支持自定义分配器、或者实现轻量级的只读共享指针。下面我们将从头实现一个简易的、线程安全的共享智能指针 MySharedPtr,并演示其使用方式与典型的使用场景。

1. 需求与设计思路

  1. 引用计数:通过内部计数器记录同一对象被多少个指针引用。计数为 0 时销毁对象。
  2. 线程安全:计数器的增减需要使用原子操作或互斥锁。
  3. 移动语义:支持 std::move,避免不必要的拷贝。
  4. 自定义删除器:允许用户传入自定义函数,类似 std::shared_ptr 的删除器。
  5. 弱引用:实现 MyWeakPtr 供观察者模式使用。

2. 代码实现

#pragma once

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

// 内部控制块:管理引用计数与删除器
template<typename T>
class ControlBlock {
public:
    std::atomic<std::size_t> use_count{1};
    std::function<void(T*)> deleter;
    T* ptr;

    explicit ControlBlock(T* p, std::function<void(T*)> del)
        : ptr(p), deleter(std::move(del)) {}

    void add_ref() noexcept {
        use_count.fetch_add(1, std::memory_order_relaxed);
    }

    void release() noexcept {
        if (use_count.fetch_sub(1, std::memory_order_acq_rel) == 1) {
            deleter(ptr);
            delete this;
        }
    }

    std::size_t use_count_val() const noexcept {
        return use_count.load(std::memory_order_acquire);
    }
};

// 前向声明
template<typename T> class MyWeakPtr;

// MySharedPtr
template<typename T>
class MySharedPtr {
    T* ptr{nullptr};
    ControlBlock <T>* ctrl{nullptr};

    friend class MyWeakPtr <T>;

public:
    // 默认构造:空指针
    MySharedPtr() noexcept = default;

    explicit MySharedPtr(T* p, std::function<void(T*)> deleter = [](T* ptr){ delete ptr; })
    {
        if (p) {
            ctrl = new ControlBlock <T>(p, std::move(deleter));
        }
    }

    // 拷贝构造
    MySharedPtr(const MySharedPtr& other) noexcept
        : ptr(other.ptr), ctrl(other.ctrl)
    {
        if (ctrl) ctrl->add_ref();
    }

    // 移动构造
    MySharedPtr(MySharedPtr&& other) noexcept
        : ptr(other.ptr), ctrl(other.ctrl)
    {
        other.ptr = nullptr;
        other.ctrl = nullptr;
    }

    // 析构
    ~MySharedPtr() noexcept {
        if (ctrl) ctrl->release();
    }

    // 赋值运算符(拷贝)
    MySharedPtr& operator=(const MySharedPtr& other) noexcept {
        if (this != &other) {
            // 增引用
            if (other.ctrl) other.ctrl->add_ref();
            // 释放旧资源
            if (ctrl) ctrl->release();
            ptr = other.ptr;
            ctrl = other.ctrl;
        }
        return *this;
    }

    // 赋值运算符(移动)
    MySharedPtr& operator=(MySharedPtr&& other) noexcept {
        if (this != &other) {
            // 释放旧资源
            if (ctrl) ctrl->release();
            // 移动指针
            ptr = other.ptr;
            ctrl = other.ctrl;
            other.ptr = nullptr;
            other.ctrl = nullptr;
        }
        return *this;
    }

    // 访问操作
    T& operator*() const noexcept { return *ptr; }
    T* operator->() const noexcept { return ptr; }
    T* get() const noexcept { return ptr; }

    std::size_t use_count() const noexcept {
        return ctrl ? ctrl->use_count_val() : 0;
    }

    bool unique() const noexcept { return use_count() == 1; }

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

// MyWeakPtr
template<typename T>
class MyWeakPtr {
    ControlBlock <T>* ctrl{nullptr};

public:
    MyWeakPtr() noexcept = default;

    explicit MyWeakPtr(const MySharedPtr <T>& shared) noexcept
        : ctrl(shared.ctrl)
    {
        if (ctrl) ctrl->add_ref(); // 这里保持用引用计数计数是共享计数吗?在本例简化
    }

    MyWeakPtr(const MyWeakPtr& other) noexcept
        : ctrl(other.ctrl)
    {
        if (ctrl) ctrl->add_ref();
    }

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

    ~MyWeakPtr() noexcept {
        if (ctrl) ctrl->release();
    }

    MyWeakPtr& operator=(const MyWeakPtr& other) noexcept {
        if (this != &other) {
            if (ctrl) ctrl->release();
            ctrl = other.ctrl;
            if (ctrl) ctrl->add_ref();
        }
        return *this;
    }

    MyWeakPtr& operator=(MyWeakPtr&& other) noexcept {
        if (this != &other) {
            if (ctrl) ctrl->release();
            ctrl = other.ctrl;
            other.ctrl = nullptr;
        }
        return *this;
    }

    // 尝试生成 MySharedPtr
    MySharedPtr <T> lock() const noexcept {
        if (ctrl && ctrl->use_count_val() > 0) {
            return MySharedPtr <T>(ctrl->ptr, ctrl->deleter); // 复制构造
        }
        return MySharedPtr <T>();
    }

    bool expired() const noexcept {
        return !ctrl || ctrl->use_count_val() == 0;
    }
};

3. 使用示例

#include <iostream>
#include "MySharedPtr.hpp"

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

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

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

    std::cout << "use_count after sp2 destroyed: " << sp1.use_count() << "\n"; // 1

    // 自定义删除器
    auto deleter = [](Node* p) {
        std::cout << "Custom deleter called for Node(" << p->value << ")\n";
        delete p;
    };
    MySharedPtr <Node> sp3(new Node(99), deleter);
    std::cout << "sp3 value: " << sp3->value << "\n";

    // MyWeakPtr 用例
    MyWeakPtr <Node> wp = sp3;
    if (auto locked = wp.lock()) {
        std::cout << "Locked weak pointer, value: " << locked->value << "\n";
    }

    return 0;
}

4. 输出结果(示例)

Node(42) constructed
use_count: 1
use_count after copy: 2
use_count after sp2 destroyed: 1
Node(99) constructed
sp3 value: 99
Locked weak pointer, value: 99
Node(42) destroyed
Custom deleter called for Node(99)
Node(99) destroyed

注:以上代码仅作教学演示,实际生产环境中请使用 std::shared_ptr / std::unique_ptr,并遵循 RAII 与线程安全原则。

5. 小结

  • 引用计数 通过 std::atomic 实现线程安全,确保多线程共享时计数的正确性。
  • 自定义删除器 让你在销毁对象时执行额外逻辑,如资源回收、日志记录等。
  • 弱指针 MyWeakPtr 通过同一个控制块实现,避免了循环引用导致的内存泄漏。
  • 移动语义 与拷贝语义并存,使智能指针既安全又高效。

通过上述实现,你可以更深入地了解 std::shared_ptr 的内部机制,并在需要时自行扩展功能。祝你编码愉快!

发表评论