014 C++的unique_ptr
作者Lou Xiao, deepseek创建时间2025-04-02 14:16:16更新时间2025-04-02 14:16:16
1. 基本概念
1.1 什么是 std::unique_ptr
- C++11引入的智能指针,用于管理动态分配对象的生命周期
- 独占所有权语义:同一时间只有一个
unique_ptr
可以拥有某个对象 - 轻量级、零开销抽象(相比原始指针几乎无额外开销)
1.2 核心特性
- 唯一所有权:不可复制,只能移动
- 自动释放:离开作用域时自动删除托管对象
- 自定义删除器:支持指定自定义的删除逻辑
- 异常安全:即使在异常情况下也能保证资源释放
2. 基本用法
2.1 创建 unique_ptr
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 方式1:直接构造
2
std::unique_ptr<int> ptr1(new int(42));
3
4
// 方式2:使用std::make_unique (C++14引入)
5
auto ptr2 = std::make_unique<int>(42); // 推荐方式
6
7
// 创建数组
8
auto arr_ptr = std::make_unique<int[]>(10); // 管理动态数组
2.2 所有权转移
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
auto ptr1 = std::make_unique<int>(42);
2
// std::unique_ptr<int> ptr2 = ptr1; // 错误!不可复制
3
4
// 正确方式:移动语义
5
std::unique_ptr<int> ptr2 = std::move(ptr1); // ptr1现在为nullptr
2.3 访问托管对象
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
auto ptr = std::make_unique<MyClass>();
2
3
// 方式1:operator*
4
MyClass& ref = *ptr;
5
6
// 方式2:operator->
7
ptr->member_function();
8
9
// 方式3:get()获取原始指针
10
MyClass* raw_ptr = ptr.get();
11
12
// 方式4:operator bool检查是否为空
13
if (ptr) { /* ptr不为空 */ }
3. 高级特性
3.1 自定义删除器
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 函数指针作为删除器
2
void FileDeleter(FILE* fp) {
3
if (fp) fclose(fp);
4
}
5
6
std::unique_ptr<FILE, decltype(&FileDeleter)> file_ptr(fopen("test.txt", "r"), &FileDeleter);
7
8
// lambda作为删除器
9
auto lambda_deleter = [](FILE* fp) { if (fp) fclose(fp); };
10
std::unique_ptr<FILE, decltype(lambda_deleter)> file_ptr2(fopen("test.txt", "r"), lambda_deleter);
11
12
// 使用std::function
13
std::unique_ptr<FILE, std::function<void(FILE*)>> file_ptr3(
14
fopen("test.txt", "r"),
15
[](FILE* fp) { if (fp) fclose(fp); }
16
);
3.2 管理数组
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 创建数组
2
auto arr_ptr = std::make_unique<int[]>(10);
3
4
// 访问数组元素
5
arr_ptr[0] = 42;
6
7
// 自定义数组删除器
8
auto arr_deleter = [](int* p) { delete[] p; };
9
std::unique_ptr<int[], decltype(arr_deleter)> arr_ptr2(new int[10], arr_deleter);
3.3 与多态类型一起使用
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
class Base { virtual ~Base() = default; /*...*/ };
2
class Derived : public Base { /*...*/ };
3
4
std::unique_ptr<Base> ptr = std::make_unique<Derived>(); // 正确,支持多态
4. 常见操作
4.1 释放所有权
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
auto ptr = std::make_unique<int>(42);
2
int* raw_ptr = ptr.release(); // ptr放弃所有权,返回原始指针
3
// 需要手动管理raw_ptr的生命周期
4.2 重置指针
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
auto ptr = std::make_unique<int>(42);
2
3
ptr.reset(); // 删除当前对象,ptr变为nullptr
4
ptr.reset(new int(100)); // 删除当前对象,接管新对象
4.3 交换指针
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
auto ptr1 = std::make_unique<int>(42);
2
auto ptr2 = std::make_unique<int>(100);
3
4
ptr1.swap(ptr2); // 交换托管对象
5
std::swap(ptr1, ptr2); // 也可以使用std::swap
5. 最佳实践
5.1 何时使用 unique_ptr
- 明确需要独占所有权的场景
- 作为工厂函数的返回类型
- 作为类的成员变量,表示独占资源
- 在容器中存储动态分配的对象
5.2 常见陷阱
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// 陷阱1:不要混用原始指针和unique_ptr
2
int* raw = new int(42);
3
std::unique_ptr<int> ptr(raw);
4
// ...
5
delete raw; // 双重删除!
6
7
// 陷阱2:不要从栈对象创建unique_ptr
8
int x = 42;
9
std::unique_ptr<int> ptr(&x); // 错误!unique_ptr会尝试delete栈对象
10
11
// 陷阱3:循环引用问题
12
struct Node {
13
std::unique_ptr<Node> next;
14
// 无法创建双向链表,因为unique_ptr不支持共享所有权
15
};
5.3 性能考虑
unique_ptr
的运行时开销几乎为零- 相比
shared_ptr
更轻量,没有引用计数的开销 make_unique
可以避免显式new带来的潜在异常安全问题
6. 与其他智能指针比较
特性 | unique_ptr | shared_ptr | weak_ptr |
---|---|---|---|
所有权语义 | 独占 | 共享 | 无 |
是否可复制 | 否 | 是 | 是 |
是否可移动 | 是 | 是 | 是 |
线程安全 | 否 | 是(引用计数) | 是 |
自定义删除器 | 支持 | 支持 | 不支持 |
管理数组 | 支持 | 不支持 | 不支持 |
循环引用 | 不适用 | 可能 | 可解决 |
7. 典型用例
7.1 工厂模式
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
class Product {
2
public:
3
virtual ~Product() = default;
4
// ...
5
};
6
7
class ConcreteProduct : public Product { /*...*/ };
8
9
std::unique_ptr<Product> createProduct() {
10
return std::make_unique<ConcreteProduct>();
11
}
7.2 Pimpl惯用法
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
// Widget.h
2
class Widget {
3
public:
4
Widget();
5
~Widget(); // 需要声明,因为unique_ptr需要完整类型
6
// ...其他接口...
7
private:
8
struct Impl;
9
std::unique_ptr<Impl> pImpl;
10
};
11
12
// Widget.cpp
13
struct Widget::Impl {
14
// 实现细节
15
};
16
17
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
18
Widget::~Widget() = default; // 必须在Impl定义后实现
7.3 异常安全资源管理
1.双击鼠标左键复制此行;2.单击复制所有代码。
1
void processFile(const std::string& filename) {
2
auto file = std::unique_ptr<FILE, decltype(&fclose)>(
3
fopen(filename.c_str(), "r"),
4
&fclose
5
);
6
7
if (!file) throw std::runtime_error("File open failed");
8
9
// 处理文件,即使抛出异常,文件也会被正确关闭
10
// ...
11
}
8. C++17/20增强
8.1 C++17改进
unique_ptr
可用于不完整类型(Pimpl模式更简单)- 新增
operator->
的constexpr支持
8.2 C++20改进
- 新增
std::make_unique_for_overwrite
,不进行值初始化 - 更好的constexpr支持
9. 总结
std::unique_ptr
是C++现代内存管理的基石,它:
1. 提供了自动内存管理,防止内存泄漏
2. 通过移动语义实现所有权转移
3. 支持自定义删除逻辑,适用于各种资源管理场景
4. 零开销抽象,性能与原始指针相当
5. 是构建更复杂资源管理策略的基础组件
在大多数情况下,std::unique_ptr
应该是动态内存管理的首选工具,只有在确实需要共享所有权时才考虑使用shared_ptr
。
附录: std::unique_ptr
的常用 API 表格
名称 | 用途 | 原型 | 代码示例 | 说明 |
---|---|---|---|---|
构造函数 | 创建 unique_ptr | explicit unique_ptr(pointer p) noexcept; unique_ptr() noexcept; unique_ptr(nullptr_t) noexcept; | std::unique_ptr<int> ptr1(new int(5)); std::unique_ptr<int> ptr2; std::unique_ptr<int> ptr3(nullptr); | 可以构造拥有对象的 unique_ptr,或构造空指针 |
移动构造函数 | 转移所有权 | unique_ptr(unique_ptr&& u) noexcept; | std::unique_ptr<int> ptr2(std::move(ptr1)); | 从另一个 unique_ptr 移动构造,原指针变为 nullptr |
析构函数 | 释放资源 | ~unique_ptr(); | 自动调用 | 自动删除管理的对象 |
operator= | 赋值操作 | unique_ptr& operator=(unique_ptr&& u) noexcept; unique_ptr& operator=(nullptr_t) noexcept; | ptr1 = std::move(ptr2); ptr1 = nullptr; | 转移所有权或重置指针 |
release | 释放所有权 | pointer release() noexcept; | int* raw_ptr = ptr.release(); | 返回裸指针并放弃所有权,调用者需手动管理 |
reset | 重置指针 | void reset(pointer p = pointer()) noexcept; | ptr.reset(new int(10)); ptr.reset(); | 删除当前对象并接管新对象,或置为空 |
swap | 交换指针 | void swap(unique_ptr& u) noexcept; | ptr1.swap(ptr2); | 交换两个 unique_ptr 的内容 |
get | 获取裸指针 | pointer get() const noexcept; | int* p = ptr.get(); | 返回管理的裸指针,但不放弃所有权 |
operator* | 解引用 | T& operator*() const; | int val = *ptr; | 访问被管理对象的引用 |
operator-> | 成员访问 | T* operator->() const noexcept; | ptr->func(); | 访问被管理对象的成员 |
operator bool | 布尔转换 | explicit operator bool() const noexcept; | if (ptr) {...} | 检查是否拥有对象 |
get_deleter | 获取删除器 | deleter_type& get_deleter() noexcept; const deleter_type& get_deleter() const noexcept; | auto& del = ptr.get_deleter(); | 获取存储的删除器对象 |
make_unique (C++14) | 创建 unique_ptr | template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args); | auto ptr = std::make_unique<int>(42); | 推荐创建方式,避免显式 new,更安全 |
数组特化版本 (unique_ptr)
名称 | 用途 | 原型 | 代码示例 | 说明 |
---|---|---|---|---|
operator[] | 数组访问 | T& operator[](size_t i) const; | ptr[0] = 10; | 访问数组元素,仅用于 unique_ptr |
构造函数 | 创建数组 | explicit unique_ptr(pointer p) noexcept; | std::unique_ptr<int[]> arr(new int[5]); | 管理动态数组 |
自定义删除器
名称 | 用途 | 原型 | 代码示例 | 说明 |
---|---|---|---|---|
自定义删除器 | 指定删除方式 | template<typename T, typename Deleter> class unique_ptr; | std::unique_ptr<FILE, decltype(&fclose)> file(fopen("a.txt", "r"), &fclose); | 可以指定自定义的删除逻辑 |
注意:
1. unique_ptr 不可复制,只能移动
2. 推荐使用 make_unique 创建 (C++14 起)
3. 默认使用 delete 或 delete[] 释放资源
4. 可以自定义删除器来管理非内存资源
文章目录