C++ 智能指针详解
智能指针是 C++11 引入的内存管理工具,位于 <memory>
头文件中,用于自动管理动态分配的内存,防止内存泄漏。主要类型如下:
1. std::unique_ptr
(独占所有权)
- 特点:唯一拥有所指对象,不可复制(可移动)
- 适用场景:独占资源所有权
- 内存开销:几乎为零(仅包装原始指针)
#include <memory>void unique_ptr_example() {// 创建 unique_ptr (C++14 推荐 make_unique)auto ptr = std::make_unique<int>(42);// 访问对象*ptr = 100;std::cout << *ptr; // 输出 100// 移动所有权auto ptr2 = std::move(ptr); // ptr 变为 nullptr// 自定义删除器(处理特殊资源)auto file_deleter = [](FILE* f) { if(f) fclose(f); };std::unique_ptr<FILE, decltype(file_deleter)> file_ptr(fopen("test.txt", "r"), file_deleter);// 离开作用域自动释放内存
}
2. std::shared_ptr
(共享所有权)
- 特点:多个指针共享对象,使用引用计数
- 适用场景:需要共享所有权的资源
- 内存开销:控制块(引用计数 + 弱计数)
void shared_ptr_example() {// 创建 shared_ptr (推荐 make_shared)auto ptr1 = std::make_shared<std::string>("Hello");// 共享所有权auto ptr2 = ptr1; // 引用计数 +1// 查看引用计数std::cout << ptr1.use_count(); // 输出 2// 自定义删除器auto arr_deleter = [](int* p) { delete[] p; };std::shared_ptr<int> arr_ptr(new int[10], arr_deleter);// 离开作用域时自动减少引用计数// 引用计数为0时释放内存
}
3. std::weak_ptr
(弱引用)
- 特点:不增加引用计数,解决循环引用问题
- 适用场景:打破 shared_ptr 循环引用
- 用法:需转换为 shared_ptr 访问对象
struct Node {std::shared_ptr<Node> next;std::weak_ptr<Node> prev; // 使用 weak_ptr 避免循环引用
};void weak_ptr_example() {auto node1 = std::make_shared<Node>();auto node2 = std::make_shared<Node>();node1->next = node2;node2->prev = node1; // 不会增加引用计数// 访问 weak_ptr 指向的对象if(auto locked = node2->prev.lock()) {// 成功转换为 shared_ptr} else {// 对象已被释放}
}
4. 智能指针对比表
特性 | unique_ptr | shared_ptr | weak_ptr |
---|---|---|---|
所有权 | 独占 | 共享 | 无(观察者) |
是否影响引用计数 | 否 | 是 | 否 |
复制语义 | 禁用(仅移动) | 允许 | 允许 |
内存开销 | 几乎为零 | 控制块(约16-32字节) | 控制块指针 |
典型应用场景 | 工厂模式返回值 | 共享数据 | 打破循环引用 |
线程安全 | 对象访问需同步 | 引用计数原子操作 | 同 shared_ptr |
5. 最佳实践与注意事项
-
优先使用
make_unique/make_shared
:// 更安全高效(单次内存分配) auto ptr = std::make_shared<MyClass>(arg1, arg2);
-
避免循环引用:
- 父对象用 shared_ptr 持有子对象
- 子对象用 weak_ptr 引用父对象
-
不要混用原始指针:
// 危险操作! MyClass* raw = new MyClass(); std::shared_ptr<MyClass> p1(raw); std::shared_ptr<MyClass> p2(raw); // 会导致双重释放
-
接口设计原则:
- 函数接收原始指针:不获取所有权
- 函数接收 unique_ptr:获取所有权
- 函数返回 unique_ptr:转移所有权
-
特殊资源管理:
// 管理数组 (C++17+) auto arr = std::make_unique<int[]>(10);// 管理自定义资源 auto custom_deleter = [](Resource* r) { cleanup(r); }; std::unique_ptr<Resource, decltype(custom_deleter)> res(new Resource, custom_deleter);
6. 性能考虑
unique_ptr
:几乎无开销,等同于原始指针shared_ptr
:- 控制块分配开销(使用 make_shared 可优化)
- 原子操作引用计数(约比非原子操作慢10倍)
- 高频访问场景:考虑 unique_ptr + 移动语义
7. C++17/20 增强
-
数组支持:
// C++17 特有 auto arr = std::make_unique<int[]>(5); arr[0] = 10;
-
std::allocate_shared
:// 自定义分配器 CustomAllocator alloc; auto ptr = std::allocate_shared<MyClass>(alloc, args...);
-
std::weak_from_this
:class MyClass : public std::enable_shared_from_this<MyClass> { public:std::weak_ptr<MyClass> weak_ref() {return weak_from_this(); // C++17} };
8. 常见错误
// 错误1:返回局部对象的智能指针
std::shared_ptr<int> create() {int value = 42;return std::make_shared<int>(value); // 正确// return &value; // 灾难!
}// 错误2:循环引用
struct A {std::shared_ptr<B> b; // 应使用 weak_ptr
};
struct B {std::shared_ptr<A> a; // 应使用 weak_ptr
};// 错误3:误用 get() 管理生命周期
void process(int* raw) { /*...*/ }auto ptr = std::make_unique<int>(10);
process(ptr.get()); // 安全
delete ptr.get(); // 灾难!
智能指针是现代 C++ 内存管理的核心工具,正确使用可消除 90% 以上的内存管理问题。