实现一个简单的自定义智能指针

在 C++11 之后,智能指针(如 std::unique_ptrstd::shared_ptr)已经成为管理资源的标准工具。然而,有时你可能需要一个更轻量、功能更专一的指针来满足某些特定需求。本文将从零实现一个最简洁的自定义智能指针 SimpleUniquePtr,并展示它的基本用法、优势与局限。

1. 设计目标

  1. 独占所有权:类似 unique_ptr,不允许多个指针共享同一块资源。
  2. 简单无引用计数:避免额外的内存开销和线程安全问题。
  3. 兼容标准容器:实现 operator*operator->get() 等,让它能直接与 STL 容器配合使用。
  4. 异常安全:确保在异常抛出时不泄露资源。

2. 基本实现

#pragma once
#include <utility>
#include <cstddef>   // size_t
#include <iostream>

template <typename T>
class SimpleUniquePtr {
private:
    T* ptr_;   // 原始裸指针

public:
    // 构造函数
    explicit SimpleUniquePtr(T* ptr = nullptr) noexcept : ptr_(ptr) {}

    // 禁止拷贝构造和拷贝赋值
    SimpleUniquePtr(const SimpleUniquePtr&) = delete;
    SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;

    // 移动构造
    SimpleUniquePtr(SimpleUniquePtr&& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }

    // 移动赋值
    SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
        if (this != &other) {
            reset();               // 先释放自己的资源
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }

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

    // 重置指针
    void reset(T* ptr = nullptr) noexcept {
        if (ptr_ != ptr) {           // 防止自我重置
            delete ptr_;
            ptr_ = ptr;
        }
    }

    // 释放所有权
    T* release() noexcept {
        T* old = ptr_;
        ptr_ = nullptr;
        return old;
    }

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

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

    // 交换
    void swap(SimpleUniquePtr& other) noexcept {
        std::swap(ptr_, other.ptr_);
    }
};

代码说明

  • 构造:接收裸指针,默认 nullptr
  • 禁止拷贝:确保独占所有权。
  • 移动:把所有权转移给新对象,旧对象置空。
  • 析构:若指针非空则 delete
  • reset/release:实现 std::unique_ptr 的常用成员函数。

3. 使用示例

#include "SimpleUniquePtr.h"

struct Person {
    std::string name;
    int age;
    Person(const std::string& n, int a) : name(n), age(a) {
        std::cout << "Person constructed: " << name << std::endl;
    }
    ~Person() {
        std::cout << "Person destructed: " << name << std::endl;
    }
};

int main() {
    SimpleUniquePtr <Person> p1(new Person("Alice", 30));

    // 访问
    std::cout << p1->name << " is " << p1->age << " years old.\n";

    // 移动所有权
    SimpleUniquePtr <Person> p2 = std::move(p1);
    if (!p1) std::cout << "p1 is now empty.\n";

    // 重置为新对象
    p2.reset(new Person("Bob", 25));

    // 释放所有权
    Person* raw = p2.release();
    std::cout << "Raw pointer obtained for: " << raw->name << "\n";
    delete raw; // 记得手动删除

    return 0;
}

运行结果示例:

Person constructed: Alice
Alice is 30 years old.
Person destructed: Alice
p1 is now empty.
Person constructed: Bob
Raw pointer obtained for: Bob
Person destructed: Bob

4. 与标准智能指针对比

功能 SimpleUniquePtr std::unique_ptr
引用计数
自定义删除器 不支持 支持
operator[] 不支持 支持(当指向数组)
emplace 不支持 支持
make_unique 手动 new std::make_unique
线程安全 仅在单线程 线程安全(移除后)

优势

  • 代码量少,易于嵌入项目的早期阶段。
  • 对资源的占用更小,没有额外的引用计数对象。

局限

  • 无法像 unique_ptr 那样自定义删除器,无法直接管理数组。
  • 缺乏 std::make_uniquestd::make_shared 等便利函数。

5. 扩展思路

  1. 自定义删除器:在模板中加入删除器类型参数 `typename Deleter = std::default_delete `,并在 `reset`、析构时调用。
  2. 数组管理:重载 operator[] 并在构造时记录数组长度。
  3. 异常安全:在 reset 时使用 try-catch,保证异常不泄露资源。
  4. 与 STL 兼容:实现 get()operator bool() 以支持 std::vector<SimpleUniquePtr<T>> 等。

6. 结语

虽然标准库已经提供了功能强大的智能指针,但在一些特殊场景(如轻量化、教学演示、或对标准库不可用的环境)下,手写一个简单的自定义智能指针既能帮助理解所有权语义,又能让代码更贴合需求。希望本文的实现与示例能为你提供一个快速入门的参考。祝编码愉快!

发表评论