智能指针是 C++11 标准引入的关键特性,用来简化资源管理并避免内存泄漏。最常见的智能指针有 std::unique_ptr、std::shared_ptr 与 std::weak_ptr,其中 std::unique_ptr 以独占所有权的方式管理资源,类似于单例对象。本文将手动实现一个简易的 unique_ptr,从中学习其内部机制与常见错误。
1. 设计目标
- 独占所有权:同一时刻只能有一个指针拥有资源。
- 移动语义:支持移动构造和移动赋值,不能拷贝。
- 自定义删除器:支持传入自定义析构函数。
- 异常安全:构造失败后不泄漏资源。
2. 基本结构
template<typename T, typename Deleter = std::default_delete<T>>
class SimpleUniquePtr {
public:
// 构造函数
explicit SimpleUniquePtr(T* ptr = nullptr, Deleter del = Deleter())
: ptr_(ptr), deleter_(del) {}
// 禁止拷贝
SimpleUniquePtr(const SimpleUniquePtr&) = delete;
SimpleUniquePtr& operator=(const SimpleUniquePtr&) = delete;
// 移动构造
SimpleUniquePtr(SimpleUniquePtr&& other) noexcept
: ptr_(other.ptr_), deleter_(std::move(other.deleter_)) {
other.ptr_ = nullptr;
}
// 移动赋值
SimpleUniquePtr& operator=(SimpleUniquePtr&& other) noexcept {
if (this != &other) {
reset();
ptr_ = other.ptr_;
deleter_ = std::move(other.deleter_);
other.ptr_ = nullptr;
}
return *this;
}
// 析构
~SimpleUniquePtr() { reset(); }
// 访问成员
T* get() const noexcept { return ptr_; }
T& operator*() const noexcept { return *ptr_; }
T* operator->() const noexcept { return ptr_; }
// 重置资源
void reset(T* ptr = nullptr) noexcept {
if (ptr_ != ptr) {
if (ptr_) deleter_(ptr_);
ptr_ = ptr;
}
}
// 移除所有权
T* release() noexcept {
T* old = ptr_;
ptr_ = nullptr;
return old;
}
// 判断是否为空
explicit operator bool() const noexcept { return ptr_ != nullptr; }
private:
T* ptr_;
Deleter deleter_;
};
3. 关键实现细节
-
移动语义
SimpleUniquePtr的移动构造函数将源对象的ptr_和deleter_迁移到目标对象,并把源对象的ptr_置为nullptr,从而确保资源只有一个所有者。noexcept标记保证移动操作不会抛异常,从而支持标准库容器对unique_ptr的内部使用。
-
自定义删除器
Deleter是模板参数,默认为 `std::default_delete `。- 通过
std::move将删除器也移动,以保持删除器的独占性。
-
异常安全
- 在构造函数中直接将传入的指针赋值给成员,若构造失败(如
new失败)会抛异常,成员ptr_仍为nullptr,不泄漏资源。 reset与析构函数均使用noexcept,避免异常进一步传播。
- 在构造函数中直接将传入的指针赋值给成员,若构造失败(如
4. 示例使用
int main() {
SimpleUniquePtr <int> p1(new int(10));
std::cout << *p1 << std::endl; // 输出 10
SimpleUniquePtr <int> p2 = std::move(p1); // 资源转移
if (!p1) std::cout << "p1 is empty\n";
p2.reset(new int(20)); // 替换资源
std::cout << *p2 << std::endl; // 输出 20
// 自定义删除器
SimpleUniquePtr <int> p3(new int(30),
[](int* ptr){ std::cout << "delete: " << *ptr << '\n'; delete ptr; });
return 0;
}
运行结果(假设输入输出正常):
10
p1 is empty
20
delete: 30
5. 常见错误与调试技巧
| 错误 | 说明 | 解决方案 |
|---|---|---|
| 双重删除 | 误将 ptr_ 复制给另一个指针,而未移动所有权。 |
确保所有 unique_ptr 操作都是移动,禁止拷贝。 |
| 悬空指针 | 通过 release() 释放所有权后忘记销毁。 |
在 release() 返回后自行管理资源或再包装成 unique_ptr。 |
| 异常泄漏 | 在 reset 或构造中抛异常,导致未释放资源。 |
通过 noexcept 和 RAII 确保析构时释放。 |
| 自定义删除器漏删 | 删除器未正确实现或未被移动。 | 确保删除器是可调用对象且在 reset 时调用。 |
6. 进一步扩展
- 数组支持:实现 `SimpleArrayUniquePtr `,类似 `std::unique_ptr`。
- 延迟销毁:结合
std::weak_ptr与std::shared_ptr,实现多级所有权。 - 多线程安全:在多线程环境下使用互斥锁保护
ptr_,但这会破坏unique_ptr的无锁特性,需谨慎使用。
7. 结语
实现自己的 unique_ptr 可以帮助你深入理解 C++ 的所有权语义、移动语义以及异常安全。通过手写代码,你可以更好地把握资源管理的细节,进而在实际项目中更自信地使用标准库提供的智能指针。祝你编码愉快!