在 C++17 之前,智能指针 std::unique_ptr 的实现已经相对成熟,但如果想深入了解其内部机制,最好的方法就是自己从零实现一个简化版。下面的示例演示了一个最小化的 unique_ptr,重点解释了模板参数、构造函数、析构函数、移动语义、以及删除器(deleter)的使用。
#include <cstddef> // std::nullptr_t
#include <utility> // std::move, std::swap
#include <type_traits> // std::enable_if, std::is_default_constructible
// 默认删除器
template<typename T>
struct default_delete {
void operator()(T* ptr) const noexcept {
delete ptr;
}
};
// 简单的 unique_ptr 实现
template<typename T, typename Deleter = default_delete<T>>
class simple_unique_ptr {
static_assert(std::is_default_constructible <Deleter>::value,
"Deleter must be default constructible");
private:
T* ptr_;
Deleter deleter_;
public:
// 默认构造:空指针
simple_unique_ptr() noexcept : ptr_(nullptr), deleter_() {}
// 直接从裸指针构造
explicit simple_unique_ptr(T* p) noexcept : ptr_(p), deleter_() {}
// 析构时删除指针
~simple_unique_ptr() noexcept {
reset();
}
// 禁止拷贝
simple_unique_ptr(const simple_unique_ptr&) = delete;
simple_unique_ptr& operator=(const simple_unique_ptr&) = delete;
// 移动构造
simple_unique_ptr(simple_unique_ptr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr;
}
// 移动赋值
simple_unique_ptr& operator=(simple_unique_ptr&& other) noexcept {
if (this != &other) {
reset(); // 先释放自身资源
ptr_ = other.ptr_; // 转移指针
deleter_ = std::move(other.deleter_);
other.ptr_ = nullptr; // 让 source 成为空
}
return *this;
}
// 访问指针
T* get() const noexcept { return ptr_; }
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
// 检查是否为空
explicit operator bool() const noexcept { return ptr_ != nullptr; }
// 重置指针,传入新指针
void reset(T* p = nullptr) noexcept {
if (ptr_) deleter_(ptr_);
ptr_ = p;
}
// 释放所有权
T* release() noexcept {
T* old = ptr_;
ptr_ = nullptr;
return old;
}
// 手动交换
void swap(simple_unique_ptr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(deleter_, other.deleter_);
}
};
// 自定义删除器示例
struct FileCloser {
void operator()(std::FILE* fp) const noexcept {
if (fp) std::fclose(fp);
}
};
int main() {
// 使用默认删除器
simple_unique_ptr <int> p1(new int(42));
if (p1) {
std::cout << *p1 << '\n';
}
// 移动构造
simple_unique_ptr <int> p2(std::move(p1));
// p1 现在为空
if (!p1) std::cout << "p1 is empty\n";
// 自定义删除器
simple_unique_ptr<std::FILE, FileCloser> file(
std::fopen("test.txt", "w"), FileCloser{}
);
// 文件将在退出时自动关闭
}
代码要点解读
-
模板参数
simple_unique_ptr<T, Deleter>允许用户自定义删除器,从而支持数组、FILE*、以及自定义资源。默认删除器default_delete<T>对于普通对象使用delete,与标准库一致。 -
构造与析构
构造函数仅保存裸指针,析构函数在销毁前调用reset(),确保资源释放。reset()负责调用删除器并将内部指针置为nullptr。 -
移动语义
unique_ptr的核心特性是所有权的唯一移动。移动构造和移动赋值实现了指针的“转移”,并把源对象的指针置为空。这样可以避免不必要的深拷贝。 -
删除器
删除器对象被存储在deleter_成员中,通过std::move在移动赋值时传递,保持所有权唯一。自定义删除器可以在构造时直接传递。 -
辅助功能
release():让调用者获取裸指针并放弃所有权。swap():交换两个unique_ptr的资源,常用于实现移动赋值时的异常安全。operator bool():可直接用于if语句判断是否为空。
与标准 std::unique_ptr 的区别
- 异常安全:示例未显式处理异常安全,但可以通过
swap与 RAII 结合实现。 - 数组支持:标准
unique_ptr有专门的数组模板(unique_ptr<T[]>),本实现省略。 - 删除器可定制化:示例已展示自定义删除器用法,保持与标准一致。
- 构造器重载:标准
unique_ptr支持更多构造器(如nullptr赋值,Deleter参数),本实现已包含最常用。
通过以上实现,你可以在自己的项目中快速复用或进一步扩展 unique_ptr 的功能,甚至添加诸如 make_unique 工厂函数、比较运算符等高级特性。祝你编码愉快!