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

在 C++ 标准库中,std::unique_ptrstd::shared_ptrstd::weak_ptr 为我们提供了强大的指针管理功能。然而,在某些特定场景下,可能需要一个更轻量级或更具业务特定功能的智能指针。本文将通过一个完整示例,演示如何从零实现一个自定义的“计数型”智能指针(类似 std::shared_ptr),并在此基础上添加日志记录、内存泄漏检测等扩展功能。


1. 设计目标

  • 引用计数:当多个指针实例共享同一块内存时,计数递增;当所有指针销毁时,释放资源。
  • 线程安全:计数操作使用 std::atomic 以支持多线程环境。
  • 日志功能:每次引用计数变化时输出日志,方便调试。
  • 内存泄漏检测:通过全局计数器,跟踪所有活跃对象数,程序结束时检查是否为 0。

2. 基础结构

#include <iostream>
#include <atomic>
#include <mutex>
#include <cassert>

template <typename T>
class MySharedPtr {
private:
    struct ControlBlock {
        std::atomic <size_t> refCount;
        T* ptr;
        std::mutex mtx;  // 用于日志同步

        ControlBlock(T* p) : refCount(1), ptr(p) {}
        ~ControlBlock() { delete ptr; }
    };

    ControlBlock* ctrl;

    // 全局活跃对象计数(用于泄漏检测)
    static std::atomic <size_t> globalActive;
  • ControlBlock 保存原始指针和引用计数,并在销毁时自动 delete 指针。
  • globalActive 用来跟踪所有 MySharedPtr 所持有的对象数量。

3. 构造与析构

public:
    // 默认构造
    MySharedPtr() : ctrl(nullptr) {}

    // 通过裸指针构造
    explicit MySharedPtr(T* rawPtr) {
        if (rawPtr) {
            ctrl = new ControlBlock(rawPtr);
            ++globalActive;
            log("Constructed");
        } else {
            ctrl = nullptr;
        }
    }

    // 拷贝构造
    MySharedPtr(const MySharedPtr& other) : ctrl(other.ctrl) {
        if (ctrl) {
            ++ctrl->refCount;
            log("Copy constructed");
        }
    }

    // 移动构造
    MySharedPtr(MySharedPtr&& other) noexcept : ctrl(other.ctrl) {
        other.ctrl = nullptr;
        log("Move constructed");
    }

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

private:
    void release() {
        if (ctrl) {
            if (--ctrl->refCount == 0) {
                log("Deleting resource");
                delete ctrl;
            } else {
                log("Released, refCount=" + std::to_string(ctrl->refCount));
            }
            ctrl = nullptr;
        }
    }
  • 每个构造函数根据情况更新计数并记录日志。
  • release() 在销毁时执行计数递减与资源释放。

4. 赋值操作

public:
    // 拷贝赋值
    MySharedPtr& operator=(const MySharedPtr& other) {
        if (this != &other) {
            release();
            ctrl = other.ctrl;
            if (ctrl) {
                ++ctrl->refCount;
                log("Copy assigned");
            }
        }
        return *this;
    }

    // 移动赋值
    MySharedPtr& operator=(MySharedPtr&& other) noexcept {
        if (this != &other) {
            release();
            ctrl = other.ctrl;
            other.ctrl = nullptr;
            log("Move assigned");
        }
        return *this;
    }

5. 访问接口

public:
    T& operator*() const { assert(ctrl && ctrl->ptr); return *(ctrl->ptr); }
    T* operator->() const { assert(ctrl && ctrl->ptr); return ctrl->ptr; }

    size_t use_count() const { return ctrl ? ctrl->refCount.load() : 0; }

    explicit operator bool() const { return ctrl && ctrl->ptr; }

6. 日志与泄漏检测

private:
    static void log(const std::string& msg) {
        std::lock_guard<std::mutex> lock(logMtx);
        std::cout << "[MySharedPtr] " << msg << "\n";
    }

    static std::mutex logMtx;
};

template<typename T>
std::atomic <size_t> MySharedPtr<T>::globalActive{0};

template<typename T>
std::mutex MySharedPtr <T>::logMtx;
  • logMtx 用于同步日志输出,避免多线程混乱。
  • 在程序结束前可以检查 globalActive 是否为 0。
int main() {
    {
        MySharedPtr <int> p1(new int(42));
        {
            MySharedPtr <int> p2 = p1;
            std::cout << "p1 use_count: " << p1.use_count() << "\n";
        }
        std::cout << "p1 use_count after p2 out of scope: " << p1.use_count() << "\n";
    }
    std::cout << "globalActive after all: " << MySharedPtr<int>::globalActive << "\n";
    return 0;
}

7. 进阶扩展

7.1 定制分配器

通过模板参数 Alloc 替换 new/delete,可支持自定义内存池。

template<typename T, typename Alloc = std::allocator<T>>
class MySharedPtr;

7.2 对齐与多继承

ControlBlock 内部加入 std::aligned_storagestd::max_align_t,以满足对齐需求。

7.3 异常安全

在构造时,如果 new 抛异常,计数不变,程序安全。


8. 小结

本文通过最小化的代码实现了一个具备引用计数、线程安全、日志记录和泄漏检测功能的自定义智能指针。该实现与 std::shared_ptr 在功能上相近,但提供了更多可自定义的钩子,适用于需要细粒度控制或特殊行为的项目。你可以在此基础上继续扩展,例如实现 weak_ptr、支持自定义 deleter、或与 RAII 容器协同工作,进一步提升代码质量与可维护性。

发表评论