C++中如何实现移动语义的自定义智能指针?

在 C++17 之前,智能指针(如 std::unique_ptr、std::shared_ptr)已经通过 move 构造函数和 move 赋值运算符实现了移动语义。然而,若我们想根据自己的需求自定义一个类似的智能指针,并且充分利用移动语义来优化性能,可以参考以下实现思路。

1. 设计目标

  • 所有权独占:像 std::unique_ptr 一样,一个指针对象在任意时刻只能有一个所有者。
  • 资源释放:默认使用 delete,但允许自定义 deleter。
  • 移动语义:支持 X&& 的移动构造与移动赋值,防止不必要的拷贝。
  • 轻量级:尽量减少额外的成员变量与内存开销。

2. 基本结构

template<typename T, typename Deleter = std::default_delete<T>>
class MovePtr {
public:
    // 默认构造,指针为空
    MovePtr() noexcept : ptr_(nullptr) {}

    // 直接构造,接收裸指针
    explicit MovePtr(T* p) noexcept : ptr_(p) {}

    // 通过 deleter 构造
    MovePtr(T* p, Deleter d) noexcept : ptr_(p), deleter_(std::move(d)) {}

    // 允许自定义 deleter 的默认构造
    explicit MovePtr(Deleter d) noexcept : deleter_(std::move(d)) {}

    // 拷贝构造禁止
    MovePtr(const MovePtr&) = delete;

    // 拷贝赋值禁止
    MovePtr& operator=(const MovePtr&) = delete;

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

    // 移动赋值
    MovePtr& operator=(MovePtr&& other) noexcept {
        if (this != &other) {
            reset();
            ptr_ = other.ptr_;
            deleter_ = std::move(other.deleter_);
            other.ptr_ = nullptr;
        }
        return *this;
    }

    // 析构
    ~MovePtr() { reset(); }

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

    // 获取裸指针
    T* get() const noexcept { return ptr_; }

    // 解引用操作符
    T& operator*() const noexcept { return *ptr_; }

    // 访问成员
    T* operator->() const noexcept { return ptr_; }

    // 隐式转换为 bool
    explicit operator bool() const noexcept { return ptr_ != nullptr; }

private:
    T* ptr_;
    Deleter deleter_{};
};

3. 关键实现细节

  1. 禁止拷贝
    通过删除拷贝构造与拷贝赋值,保证对象唯一所有权。

  2. 移动构造与移动赋值

    • 移动构造时,将源对象的指针与 deleter 迁移到新对象,并将源指针置为 nullptr
    • 移动赋值时,先释放自身已有资源,再迁移,最后置源对象为空。
  3. 自定义 deleter

    • Deleter 默认使用 `std::default_delete `,可以像 `std::unique_ptr` 一样接受任何可调用对象。
    • 在构造时若传入 deleter,则复制或移动该 deleter,保证其生命周期足够长。
  4. 异常安全

    • 所有成员函数都使用 noexcept,满足移动构造/赋值的标准要求。
    • reset() 采用 try-catch 机制(此处省略),以避免 deleter 抛异常导致对象析构时崩溃。

4. 使用示例

// 自定义 deleter
auto array_deleter = [](int* p){ std::cout << "delete[] array\n"; delete[] p; };

int main() {
    MovePtr <int> p1(new int(42));          // 默认 deleter
    MovePtr<int[]> p2(new int[10], array_deleter); // 自定义 deleter

    // 移动赋值
    MovePtr <int> p3 = std::move(p1);        // p1 现在为空

    // 访问
    if (p3) std::cout << *p3 << '\n';      // 输出 42

    // 资源释放
    p2.reset(); // 手动释放
    // p2 析构时会再次释放,防止 double delete,需要在 reset 后置 nullptr
}

5. 性能考虑

  • 内存占用MovePtr 仅包含指针与 deleter,大小与 std::unique_ptr 相当。
  • 移动效率:移动构造与赋值只涉及指针与 deleter 的拷贝,复杂度为 O(1)。
  • 异常安全:在移动赋值过程中,若 reset() 或 deleter 抛异常,使用 noexcept 标记会导致程序终止,因此建议 deleter 不抛异常。

6. 扩展功能

  • 观察器模式:添加 use_count 实现共享所有权(类似 std::shared_ptr)。
  • 线程安全:为 deleter 和引用计数加锁。
  • 内存池:自定义分配器,用于高频分配释放。

7. 结语

通过上述实现,我们可以在不依赖标准库的前提下,得到一个具备移动语义、可自定义 deleter 的轻量级智能指针。它在 C++ 语言中保持了与 std::unique_ptr 的一致性,同时提供了更大的灵活性和可扩展性。若需进一步优化性能或功能,可在此基础上进行定制化扩展。

发表评论