在 C++17 之后,智能指针已经非常成熟且易于使用,std::unique_ptr 和 std::shared_ptr 等已覆盖大多数需求。然而,学习如何从零实现自己的智能指针,有助于深入理解内存管理、所有权语义和 RAII 机制。下面我们用最简洁的方式实现一个类似 std::unique_ptr 的 MyUniquePtr,并展示它的使用场景。
1. 设计目标
- 单一所有权:只能有一个指针实例管理同一块内存。
- 自动释放:析构时自动
delete对象。 - 移动语义:支持移动构造和移动赋值,防止无意中复制。
- 防止拷贝:拷贝构造和拷贝赋值被禁用。
- 提供原始指针访问:通过
operator*,operator->,get()等。
2. 代码实现
#include <utility> // std::swap
#include <cassert> // assert
template <typename T>
class MyUniquePtr {
public:
// 默认构造,指针为空
MyUniquePtr() noexcept : ptr_(nullptr) {}
// 从裸指针构造
explicit MyUniquePtr(T* ptr) noexcept : ptr_(ptr) {}
// 析构:释放资源
~MyUniquePtr() { reset(); }
// 禁止拷贝
MyUniquePtr(const MyUniquePtr&) = delete;
MyUniquePtr& operator=(const MyUniquePtr&) = delete;
// 移动构造
MyUniquePtr(MyUniquePtr&& other) noexcept : ptr_(other.ptr_) {
other.ptr_ = nullptr;
}
// 移动赋值
MyUniquePtr& operator=(MyUniquePtr&& other) noexcept {
if (this != &other) {
reset(); // 先释放自己的资源
ptr_ = other.ptr_; // 拿走资源
other.ptr_ = nullptr; // 让 source 成为空指针
}
return *this;
}
// 访问被管理对象
T& operator*() const noexcept { assert(ptr_); return *ptr_; }
T* operator->() const noexcept { assert(ptr_); return ptr_; }
T* get() const noexcept { return ptr_; }
// 检查是否为空
explicit operator bool() const noexcept { return ptr_ != nullptr; }
// 释放并置空
void reset(T* new_ptr = nullptr) noexcept {
if (ptr_) delete ptr_;
ptr_ = new_ptr;
}
// 取回裸指针,转移所有权
T* release() noexcept {
T* temp = ptr_;
ptr_ = nullptr;
return temp;
}
// 用新指针替换旧指针
void swap(MyUniquePtr& other) noexcept {
std::swap(ptr_, other.ptr_);
}
private:
T* ptr_;
};
3. 使用示例
#include <iostream>
#include <string>
int main() {
// 1. 简单使用
MyUniquePtr <int> p1(new int(42));
std::cout << *p1 << std::endl; // 输出 42
// 2. 移动构造
MyUniquePtr <int> p2(std::move(p1));
if (!p1) std::cout << "p1 is empty\n";
// 3. 移动赋值
MyUniquePtr<std::string> p3(new std::string("Hello"));
MyUniquePtr<std::string> p4;
p4 = std::move(p3);
std::cout << *p4 << std::endl; // 输出 Hello
// 4. reset 与 release
p4.reset(); // 释放 string
p4 = MyUniquePtr<std::string>(new std::string("C++"));
std::cout << *p4 << std::endl; // 输出 C++
int* raw = p4.release(); // 取回裸指针
delete raw; // 手动 delete
return 0;
}
4. 关键点回顾
- RAII:构造时绑定资源,析构时释放,保证异常安全。
- 所有权移动:移动构造/赋值通过
std::move将资源转移,防止资源泄漏。 - 禁用拷贝:复制会导致多重 delete,因而被删除。
- swap:提供原子交换,方便实现容器等高级功能。
- release:在特殊场景下需要把所有权交给外部管理时使用。
5. 延伸阅读
- shared_ptr:多所有者、引用计数实现。可以在
MyUniquePtr的基础上增加计数器实现。 - make_unique:返回 `MyUniquePtr `,避免裸指针泄漏。可自行实现。
- 自定义 deleter:支持对数组、文件句柄等非普通
delete的资源进行正确释放。
通过上述实现,你可以在不依赖标准库的情况下,掌握智能指针的核心机制,进一步理解 C++ 内存管理的哲学。