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

  1. 明确需要独占所有权的场景
  2. 作为工厂函数的返回类型
  3. 作为类的成员变量,表示独占资源
  4. 在容器中存储动态分配的对象

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_ptrshared_ptrweak_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_ptrexplicit 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_ptrtemplate<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. 可以自定义删除器来管理非内存资源

文章目录