一、std::unique_ptr(独占式智能指针)
1. 核心特性
- 独占所有权 :同一时间只有一个
unique_ptr指向某个对象,不可拷贝(copy),仅可移动(move); - 轻量级(无额外引用计数开销),效率接近裸指针;
- 适用场景:单个所有者管理对象(如函数返回值、局部动态对象、容器元素)。
2. 定义方式:
| 定义形式 | 说明 |
|---|---|
unique_ptr<T> ptr; |
定义空的 unique_ptr(不指向任何对象) |
| unique_ptr<T> ptr(已有裸指针); | 该裸指针必须是由new分配的,否则delete的时候报错 |
unique_ptr<T> ptr(new T); std::unique_ptr<T[]> ptr(new T[N]); |
绑定到动态分配的 T 类型对象(C++11 基础方式) |
auto ptr = make_unique<T>(args); auto ptr = std::make_unique<T[]>(N); |
C++14 推荐方式(更安全,避免内存泄漏),args 是 T 的构造参数 |
3. 常用方法
| 方法 | 功能 |
|---|---|
ptr.get() |
返回管理的裸指针(仅临时使用,避免长期持有) |
ptr.release() |
释放所有权(返回裸指针,不会释放内存,需手动处理) |
ptr.reset() |
释放当前对象内存,重置为空;reset(new T) 则释放旧对象,指向新对象 |
ptr.swap() |
交换两个 unique_ptr 管理的对象 |
operator* |
解引用,访问指向的对象(如 *ptr = 10;) |
operator-> |
访问对象的成员(如 ptr->func();) |
cpp
#include <iostream>
#include <memory> // 必须包含
// 自定义类示例
class Test {
public:
Test(int v) : val(v) { std::cout << "Test 构造:" << val << "\n"; }
~Test() { std::cout << "Test 析构:" << val << "\n"; }
void show() { std::cout << "值:" << val << "\n"; }
private:
int val;
};
int main() {
// 1. 定义并初始化(C++11 方式)
std::unique_ptr<Test> ptr1(new Test(10));
ptr1->show(); // 访问成员函数
// 2. C++14 推荐:make_unique(更安全,避免内存泄漏)
auto ptr2 = std::make_unique<Test>(20);
(*ptr2).show(); // 解引用访问
// 3. 移动语义(唯一的"拷贝"方式)
std::unique_ptr<Test> ptr3 = std::move(ptr1); // ptr1 变为空,ptr3 接管
if (!ptr1) std::cout << "ptr1 已空\n";
ptr3->show();
// 4. 重置(释放旧对象,指向新对象)
ptr3.reset(new Test(30)); // 旧对象(val=10)析构,指向 val=30 的新对象
// 5. 释放所有权(返回裸指针,需手动 delete)
Test* raw_ptr = ptr3.release();
delete raw_ptr; // 必须手动释放,否则内存泄漏
return 0; // 作用域结束,ptr2 自动析构(val=20 释放)
}
二、std::shared_ptr(共享式智能指针)
1. 核心特性
- 共享所有权 :多个
shared_ptr可指向同一对象,通过引用计数管理; - 引用计数为 0 时,自动释放对象内存;
- 可拷贝、可赋值,拷贝时引用计数 +1,析构 / 重置时引用计数 -1;
- 适用场景:多个所有者共享同一对象(如容器、多线程共享资源)
2. 定义方式:
| 定义形式 | 说明 |
|---|---|
shared_ptr<T> ptr; |
空的 shared_ptr |
std::shared_ptr<T> ptr(已有裸指针); |
接管已存在的动态裸指针 |
shared_ptr<T> ptr(new T); std::shared_ptr<T[]> ptr(new T[N]); |
绑定动态对象(不推荐,存在内存泄漏风险) |
auto ptr = make_shared<T>(args); auto ptr = std::make_shared<T[]>(N); |
推荐方式(更高效,内存分配一次,避免泄漏) |
3. 常用方法
| 方法 | 功能 |
|---|---|
ptr.use_count() |
返回当前引用计数(调试用) |
ptr.unique() |
判断是否为唯一所有者(引用计数 == 1 时返回 true) |
ptr.reset() |
释放当前对象的所有权(引用计数 -1),重置为空 |
ptr.lock() |
(weak_ptr 方法)尝试获取可用的 shared_ptr(非 expired 时有效) |
ptr.expired() |
(weak_ptr 方法)判断所指对象是否已释放(引用计数 == 0) |
| (继承 unique_ptr) | get()、operator*、operator-> 等 |
cpp
#include <iostream>
#include <memory>
class Test {
public:
Test(int v) : val(v) { std::cout << "Test 构造:" << val << "\n"; }
~Test() { std::cout << "Test 析构:" << val << "\n"; }
int val;
};
int main() {
// 1. 推荐方式:make_shared
std::shared_ptr<Test> ptr1 = std::make_shared<Test>(100);
std::cout << "引用计数:" << ptr1.use_count() << "\n"; // 输出 1
// 2. 拷贝:引用计数 +1
std::shared_ptr<Test> ptr2 = ptr1;
std::cout << "引用计数:" << ptr1.use_count() << "\n"; // 输出 2
// 3. 赋值:ptr3 接管,引用计数 +1
std::shared_ptr<Test> ptr3;
ptr3 = ptr2;
std::cout << "引用计数:" << ptr1.use_count() << "\n"; // 输出 3
// 4. 重置:ptr3 释放所有权,引用计数 -1
ptr3.reset();
std::cout << "引用计数:" << ptr1.use_count() << "\n"; // 输出 2
// 5. 唯一所有者判断
ptr2.reset();
std::cout << "是否唯一:" << ptr1.unique() << "\n"; // 输出 1(true)
return 0; // ptr1 析构,引用计数 0,对象释放
}
三、std::weak_ptr(弱引用智能指针)
1. 核心特性
- 弱引用 :指向
shared_ptr管理的对象,但不增加引用计数; - 不拥有对象所有权,仅作为 "观察者",避免
shared_ptr的循环引用问题; - 无法直接访问对象,需通过
lock()方法转为shared_ptr后使用。
2. 定义方式:
| 定义形式 | 说明 |
|---|---|
std::weak_ptr<T> ptr; |
空初始化:ptr 不观察任何对象,expired() 返回 true |
std::weak_ptr<T> ptr(shared_ptr对象); |
观察 shared_ptr 管理的对象,引用计数不变 |
cpp
#include <iostream>
#include <memory>
// 循环引用问题场景
struct Node {
// 错误:两个 shared_ptr 互相引用,引用计数永远不为 0,内存泄漏
// std::shared_ptr<Node> next;
// 正确:用 weak_ptr 避免增加引用计数
std::weak_ptr<Node> next;
~Node() { std::cout << "Node 析构\n"; }
};
int main() {
std::shared_ptr<Node> node1 = std::make_shared<Node>();
std::shared_ptr<Node> node2 = std::make_shared<Node>();
// 互相引用
node1->next = node2; // weak_ptr 赋值,node2 引用计数不变
node2->next = node1; // weak_ptr 赋值,node1 引用计数不变
// 尝试访问 weak_ptr 指向的对象(需 lock())
std::shared_ptr<Node> temp = node1->next.lock();
if (temp) {
std::cout << "node1->next 有效\n";
}
return 0; // node1、node2 析构,引用计数 0,对象释放
}
四、总结:
| 智能指针 | 所有权 | 拷贝性 | 引用计数 | 核心场景 |
|---|---|---|---|---|
| unique_ptr | 独占 | 不可拷贝,可移动 | 无 | 单个所有者、局部对象 |
| shared_ptr | 共享 | 可拷贝、可赋值 | 有 | 多所有者、共享资源 |
| weak_ptr | 无 | 可拷贝、可赋值 | 无 | 解决 shared_ptr 循环引用 |
1. 禁止的操作
- 不要将同一个裸指针赋值给多个
unique_ptr(会导致重复释放); - 不要用
get()返回的裸指针手动delete(智能指针会重复释放); - 避免
shared_ptr循环引用(用weak_ptr解决); - 不要在多线程中直接修改
shared_ptr本身(引用计数操作线程安全,但对象访问需加锁)。
2. 最佳实践
| 场景 | 推荐智能指针 |
|---|---|
| 单个所有者管理对象 | unique_ptr |
| 多个所有者共享对象 | shared_ptr |
| 解决 shared_ptr 循环引用 | weak_ptr |
| 动态数组 | unique_ptr<T[]>(C++11)/ shared_ptr<T[]> |
| 函数返回动态对象 | unique_ptr(高效) |
五、智能指针别的定义方式:
1. unique_ptr
| 定义形式 | 说明 | 注意事项(版本 / 风险) |
|---|---|---|
std::unique_ptr<T> ptr; |
空初始化:ptr 不指向任何对象,ptr == nullptr |
C++11+;默认删除器 std::default_delete<T> |
std::unique_ptr<T> ptr(nullptr); |
显式空初始化(等价于上一种) | C++11+;可读性更强 |
std::unique_ptr<T> ptr(new T); |
绑定新动态分配的单个 T 类型裸指针 | C++11+;裸指针必须是 new 分配,避免重复接管 |
std::unique_ptr<T> ptr(已有裸指针); |
接管已存在的动态裸指针(如函数返回的裸指针) | C++11+;裸指针必须是 new 分配,且仅能被一个 unique_ptr 接管 |
std::unique_ptr<T[]> ptr(new T[N]); |
绑定动态数组裸指针(数组版) | C++11+;自动调用 delete[],支持下标访问 ptr[i] |
auto ptr = std::make_unique<T>(args...); |
直接创建单个 T 对象并初始化(推荐) | C++14+;更安全(避免内存泄漏),args 是 T 的构造参数 |
auto ptr = std::make_unique<T[]>(N); |
直接创建长度为 N 的 T 类型动态数组 | C++14+;数组元素默认初始化(如 int 数组值为 0) |
auto ptr = std::make_unique<T[]>({v1, v2, ...}); |
创建动态数组并初始化元素 | C++20+;仅支持固定初始值列表 |
std::unique_ptr<T> ptr(std::move(另一个unique_ptr)); |
移动初始化:接管另一个 unique_ptr 的所有权 | C++11+;原 unique_ptr 变为空,不可拷贝仅可移动 |
std::unique_ptr<T, Deleter> ptr(new T, 自定义删除器); |
自定义删除器:替换默认的 delete 逻辑 |
C++11+;删除器需满足可调用,如 std::function<void(T*)> 或 lambda |
std::unique_ptr<T[], Deleter> ptr(new T[N], 自定义数组删除器); |
数组版 + 自定义删除器 | C++11+;删除器需适配数组(调用 delete[] 或自定义释放逻辑) |
std::unique_ptr<T> ptr(std::auto_ptr<T>(std::move(ap))); |
从废弃的 auto_ptr 转换(兼容用) | C++11+;auto_ptr 已废弃,仅用于老代码迁移 |
2. shared_ptr
| 定义形式 | 说明 | 注意事项(版本 / 风险) |
|---|---|---|
std::shared_ptr<T> ptr; |
空初始化:ptr 不指向任何对象,引用计数 0 | C++11+;默认删除器 std::default_delete<T> |
std::shared_ptr<T> ptr(nullptr); |
显式空初始化(等价于上一种) | C++11+;可读性更强 |
std::shared_ptr<T> ptr(new T); |
绑定新动态分配的单个 T 类型裸指针 | C++11+;不推荐(存在内存泄漏风险),优先用 make_shared |
std::shared_ptr<T> ptr(已有裸指针); |
接管已存在的动态裸指针 | C++11+;禁止将同一裸指针赋值给多个 shared_ptr(重复计数导致重复释放) |
std::shared_ptr<T> ptr(new T[N], std::default_delete<T[]>()); |
绑定动态数组(C++17 前需指定数组删除器) | C++11+;C++17 后可省略删除器,默认支持数组 |
std::shared_ptr<T[]> ptr(new T[N]); |
数组版初始化(默认删除器支持数组) | C++17+;支持下标访问 ptr[i] |
auto ptr = std::make_shared<T>(args...); |
直接创建单个 T 对象并初始化(推荐) | C++11+;内存分配更高效(一次分配对象 + 引用计数),args 是构造参数 |
auto ptr = std::make_shared<T[]>(N); |
直接创建长度为 N 的 T 类型动态数组 | C++20+;数组元素默认初始化 |
std::shared_ptr<T> ptr(std::move(另一个shared_ptr)); |
移动初始化:接管另一个 shared_ptr 的所有权,引用计数不变 | C++11+;原 shared_ptr 变为空 |
std::shared_ptr<T> ptr(另一个shared_ptr); |
拷贝初始化:共享所有权,引用计数 +1 | C++11+;可拷贝是 shared_ptr 核心特征 |
std::shared_ptr<T> ptr(new T, 自定义删除器); |
自定义删除器:替换默认的 delete 逻辑 |
C++11+;删除器不影响 shared_ptr 类型(类型擦除) |
std::shared_ptr<T> ptr(std::unique_ptr<T>(std::move(up))); |
从 unique_ptr 转换:接管其所有权,引用计数设为 1 | C++11+;unique_ptr 必须移动,转换后变为空 |
std::shared_ptr<T> ptr(weak_ptr.lock()); |
从 weak_ptr 转换:若对象未释放则获取 shared_ptr,否则返回空 | C++11+;需先判断 ptr 是否为空(避免悬空) |
3. weak_ptr
| 定义形式 | 说明 | 注意事项(版本 / 风险) |
|---|---|---|
std::weak_ptr<T> ptr; |
空初始化:ptr 不观察任何对象,expired() 返回 true |
C++11+;无引用计数,不拥有对象所有权 |
std::weak_ptr<T> ptr(nullptr); |
显式空初始化(等价于上一种) | C++11+;可读性更强 |
std::weak_ptr<T> ptr(shared_ptr对象); |
观察 shared_ptr 管理的对象,引用计数不变 | C++11+;核心用法(解决循环引用) |
std::weak_ptr<T> ptr(另一个weak_ptr); |
拷贝初始化:观察同一个对象 | C++11+;引用计数不变 |
std::weak_ptr<T> ptr(std::move(另一个weak_ptr)); |
移动初始化:观察同一个对象,原 weak_ptr 变为空 | C++11+;仅转移 weak_ptr 自身的资源,不影响被观察对象 |