在现代 C++ 开发中,资源管理是程序员必须认真对待的一个重要问题。标准库提供了 std::unique_ptr、std::shared_ptr 等智能指针来简化内存管理,但在某些场景下,标准实现并不能完全满足业务需求。比如你需要一个拥有额外元数据或自定义删除器的智能指针,或者想在不使用标准库的项目中实现类似功能。本文将带你从头实现一个基本的 UniquePtr,并演示如何扩展它以满足更复杂的需求。
1. 设计目标
- 单一所有权:与
std::unique_ptr一致,指针对象在任意时刻只能被一个UniquePtr拥有。 - RAII:对象销毁时自动释放资源,避免内存泄漏。
- 移动语义:支持移动构造和移动赋值,转移所有权。
- 防止拷贝:禁止拷贝构造和拷贝赋值。
- 可定制删除器:默认使用
delete,但可以通过模板参数传入自定义删除器。 - 扩展性:后续可添加如引用计数、线程安全等功能。
2. 基础实现
#include <utility> // std::swap
#include <cstddef> // std::nullptr_t
// 默认删除器,使用 delete 释放单个对象
struct DefaultDeleter {
template <typename T>
void operator()(T* ptr) const {
delete ptr;
}
};
template <typename T, typename Deleter = DefaultDeleter>
class UniquePtr {
public:
// 类型别名,便于用户访问
using pointer = T*;
using element_type = T;
/*------------------------ 构造与析构 ------------------------*/
explicit UniquePtr(pointer ptr = nullptr, Deleter d = Deleter())
: ptr_(ptr), deleter_(std::move(d)) {}
~UniquePtr() {
reset();
}
/*------------------------ 禁止拷贝 ------------------------*/
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
/*------------------------ 移动语义 ------------------------*/
UniquePtr(UniquePtr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr;
}
UniquePtr& operator=(UniquePtr&& other) noexcept {
if (this != &other) {
reset();
ptr_ = other.ptr_;
deleter_ = std::move(other.deleter_);
other.ptr_ = nullptr;
}
return *this;
}
/*------------------------ 成员访问 ------------------------*/
pointer get() const noexcept { return ptr_; }
element_type& operator*() const noexcept { return *ptr_; }
pointer operator->() const noexcept { return ptr_; }
explicit operator bool() const noexcept { return ptr_ != nullptr; }
/*------------------------ 资源释放与重置 ------------------------*/
void reset(pointer ptr = nullptr) noexcept {
if (ptr_ != ptr) {
if (ptr_) deleter_(ptr_);
ptr_ = ptr;
}
}
/*------------------------ 交换 ------------------------*/
void swap(UniquePtr& other) noexcept {
std::swap(ptr_, other.ptr_);
std::swap(deleter_, other.deleter_);
}
private:
pointer ptr_;
Deleter deleter_;
};
/*------------------------ 友元 swap ------------------------*/
template <typename T, typename D>
void swap(UniquePtr<T, D>& a, UniquePtr<T, D>& b) noexcept {
a.swap(b);
}
说明
reset()在析构时被调用,确保资源被释放。若传入新指针,旧指针先被销毁。operator*与operator->通过ptr_访问对象成员。- 移动构造/赋值后,源对象的指针被置为
nullptr,防止二次删除。
3. 自定义删除器示例
struct FileDeleter {
void operator()(FILE* f) const {
if (f) fclose(f);
}
};
UniquePtr<FILE, FileDeleter> filePtr(fopen("log.txt", "w"));
此时,当 filePtr 超出作用域时会自动调用 fclose 关闭文件句柄。
4. 与标准库的互操作
#include <memory> // std::unique_ptr
UniquePtr <int> myPtr(new int(10));
// 与 std::unique_ptr 的转换
std::unique_ptr <int> stdPtr = std::make_unique<int>(20);
// 通过标准库的 std::move 转移所有权
UniquePtr <int> anotherPtr = std::move(myPtr);
如果需要把 UniquePtr 转成 std::unique_ptr,可以提供一个显式转换函数:
template <typename T, typename D>
std::unique_ptr<T, D> toStdUniquePtr(UniquePtr<T, D>&& up) {
return std::unique_ptr<T, D>(up.release(), up.getDeleter());
}
(注意 release() 与 getDeleter() 需要在类中实现,本文未展示,但可根据需求添加。)
5. 进一步扩展
- 线程安全:在移动/重置时使用互斥锁。
- 引用计数:改造为
SharedPtr,实现std::shared_ptr的功能。 - 自定义分配器:在构造时接受分配器,用于自定义内存池。
- 多维数组:为数组类型
T[]提供专门的删除器。
6. 小结
本文提供了一个完整的 UniquePtr 实现,从基本功能到自定义删除器的使用,再到与标准库的互操作。通过这种方式,你可以在不依赖 `