如何在C++中实现自定义智能指针的复制与移动语义

在现代 C++ 开发中,智能指针是管理资源的首选工具。虽然标准库已经提供了 std::unique_ptrstd::shared_ptrstd::weak_ptr 等,但在某些特殊场景下我们可能需要自定义自己的智能指针。例如,想要在指针上附加额外的日志信息、实现线程安全的引用计数,或者在指针失效时执行自定义回调。下面,我们将逐步实现一个名为 MySmartPtr 的简易智能指针,并演示如何在其内部实现复制与移动语义。


1. 设计目标

  • 单一拥有者:与 std::unique_ptr 类似,MySmartPtr 只允许一个实例拥有同一块资源。
  • 自定义回调:在资源释放时执行用户提供的函数。
  • 复制与移动:支持显式移动构造/赋值;复制构造/赋值被删除,防止误用。
  • 异常安全:保证在异常抛出时不泄漏资源。

2. 基础框架

#include <iostream>
#include <functional>
#include <utility>

template <typename T>
class MySmartPtr {
public:
    // 构造函数:接收裸指针和可选的销毁回调
    explicit MySmartPtr(T* ptr = nullptr,
                        std::function<void(T*)> deleter = [](T* p){ delete p; })
        : ptr_(ptr), deleter_(std::move(deleter)) {}

    // 析构函数:调用回调释放资源
    ~MySmartPtr() { reset(); }

    // 禁止复制
    MySmartPtr(const MySmartPtr&) = delete;
    MySmartPtr& operator=(const MySmartPtr&) = delete;

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

    // 移动赋值
    MySmartPtr& operator=(MySmartPtr&& other) noexcept {
        if (this != &other) {
            reset();                 // 先释放自己持有的资源
            ptr_ = other.ptr_;       // 转移指针
            deleter_ = std::move(other.deleter_);
            other.ptr_ = nullptr;    // 让源对象为空
        }
        return *this;
    }

    // 访问指针
    T* get() const noexcept { return ptr_; }
    T& operator*() const noexcept { return *ptr_; }
    T* operator->() const noexcept { return ptr_; }

    // 资源释放
    void reset(T* new_ptr = nullptr) noexcept {
        if (ptr_) {
            deleter_(ptr_);
        }
        ptr_ = new_ptr;
    }

    // 判断是否为空
    explicit operator bool() const noexcept { return ptr_ != nullptr; }

private:
    T* ptr_;
    std::function<void(T*)> deleter_;
};

说明

  • 构造函数默认使用 delete 释放资源,但用户可以传入自定义回调,例如 `std::default_delete ` 或 `free`。
  • reset 方法保证即使在异常发生时也能安全释放资源。
  • 移动构造/赋值使用 noexcept 标记,确保在容器搬迁时不会抛异常。

3. 使用示例

struct Widget {
    int id;
    Widget(int i) : id(i) { std::cout << "Widget " << id << " constructed\n"; }
    ~Widget() { std::cout << "Widget " << id << " destructed\n"; }
};

int main() {
    // 1. 创建智能指针
    MySmartPtr <Widget> ptr1(new Widget(42));
    std::cout << "ptr1 id: " << ptr1->id << "\n";

    // 2. 移动 ptr1 到 ptr2
    MySmartPtr <Widget> ptr2 = std::move(ptr1);
    if (!ptr1) std::cout << "ptr1 is empty after move\n";

    // 3. 自定义销毁回调
    MySmartPtr <Widget> ptr3(new Widget(99),
                            [](Widget* w){ 
                                std::cout << "Custom delete for widget " << w->id << "\n";
                                delete w;
                            });

    // 4. 资源释放
    ptr2.reset();   // 立即销毁
    // ptr3 在 main 结束时自动销毁,触发自定义回调
}

输出

Widget 42 constructed
ptr1 id: 42
ptr1 is empty after move
Widget 99 constructed
Widget 42 destructed
Custom delete for widget 99
Widget 99 destructed

4. 进一步扩展

  1. 线程安全:在 ptr_ 旁边加一个 std::atomic<T*>std::mutex,确保多线程访问时的安全。
  2. 可变引用计数:类似 std::shared_ptr,实现引用计数机制,使多实例共享同一资源。
  3. 与 RAII 结合:在类中使用 MySmartPtr 成员,确保类对象的生命周期结束时自动释放资源。
  4. 自定义分配器:允许用户传入自定义内存分配器,实现内存池或对齐分配。

5. 小结

本文通过实现 MySmartPtr 展示了如何在 C++ 中手工管理资源,并实现复制与移动语义。虽然标准库已提供强大的智能指针,但在特殊需求下,自定义实现能够给你更多控制权。只需关注构造、析构、移动和异常安全即可。希望这份实现对你在实际项目中设计自定义资源管理器有所帮助。

发表评论