C++:智能指针
C++智能指针是STL提供的自动内存管理工具,基于RAII机制避免内存泄漏和悬挂指针等问题。主要包含三种类型:unique_ptr(独占所有权,不可复制只能移动)、shared_ptr(共享所有权,通过引用计数管理)和weak_ptr(弱引用,解决循环引用)。使用时应优先选择make_unique/make_shared创建,避免直接操作原始指针。智能指针能显著提升代码安全性,其中unique_p
前言:智能指针是 C++ 标准库(STL)提供的一种自动管理动态内存的工具,其核心作用是避免手动调用 new
/delete
导致的内存泄漏、悬挂指针、重复释放等问题。它通过 “资源获取即初始化(RAII)” 机制,在智能指针的生命周期结束时(如超出作用域)自动释放所管理的内存。
一、为什么需要智能指针?
手动管理动态内存(
new
/delete
)存在三大风险:
- 内存泄漏:忘记调用
delete
,导致内存永远无法释放。- 重复释放:对同一指针多次调用
delete
,导致程序崩溃。- 悬挂指针:指针指向的内存已被释放,但指针未置空,后续访问导致未定义行为。
示例(手动管理的问题):
void func() { int* ptr = new int(10); // 动态分配 if (some_condition) { return; // 忘记delete,内存泄漏 } delete ptr; // 若上面return,此处不执行 }
智能指针通过自动释放内存解决这些问题,即使函数提前返回或抛出异常,也能保证内存被正确释放。
二、C++ 中的三种智能指针
C++11 及后续标准定义了三种常用智能指针,均位于
<memory>
头文件中:
std::unique_ptr
:独占所有权的智能指针(唯一拥有对象)。std::shared_ptr
:共享所有权的智能指针(多人共享对象)。std::weak_ptr
:辅助shared_ptr
的弱引用指针(不影响所有权)。1. std::unique_ptr:独占所有权
核心特点:
- 同一时间,只有一个
unique_ptr
能拥有对象的所有权。- 无法复制(
copy
),只能移动(move
),确保所有权唯一。- 当
unique_ptr
销毁时(如超出作用域),自动释放所管理的对象。用法示例:
#include <memory> // 必须包含的头文件 #include <iostream> using namespace std; int main() { // 方式1:用new初始化(不推荐,尽量用make_unique) unique_ptr<int> up1(new int(10)); // 方式2:用make_unique创建(C++14+,更安全高效) unique_ptr<int> up2 = make_unique<int>(20); // 推荐! // 访问对象:用*或->(类似普通指针) cout << *up1 << endl; // 输出10 cout << *up2 << endl; // 输出20 // 所有权转移(用move,原指针变为nullptr) unique_ptr<int> up3 = move(up2); // up2失去所有权,变为空 if (up2 == nullptr) { cout << "up2 is null" << endl; // 输出此句 } // 无需手动delete,超出作用域时自动释放 return 0; }
适用场景:
- 管理单个对象的所有权(如局部动态对象、函数返回动态对象)。
- 作为容器元素(避免复制,适合
vector<unique_ptr<T>>
)。- 替代
auto_ptr
(auto_ptr
已被弃用,因存在复制语义缺陷)。2. std::shared_ptr:共享所有权
核心特点:
- 多个
shared_ptr
可共享同一对象的所有权,通过引用计数(reference count)跟踪对象被引用的次数。- 当最后一个
shared_ptr
销毁时,引用计数变为 0,自动释放对象。- 支持复制(复制时引用计数 + 1),也支持移动。
引用计数工作原理:
- 每个
shared_ptr
管理的对象,都对应一个 “控制块”(存储引用计数和其他信息)。- 当复制
shared_ptr
时(如sp2 = sp1
),引用计数 + 1。- 当
shared_ptr
销毁或重置时,引用计数 - 1。- 引用计数为 0 时,控制块和对象内存被释放。
用法示例:
#include <memory> #include <iostream> using namespace std; int main() { // 方式1:用new初始化(不推荐) shared_ptr<int> sp1(new int(100)); // 方式2:用make_shared创建(推荐,效率更高,一次分配内存) shared_ptr<int> sp2 = make_shared<int>(200); // 复制:引用计数+1 shared_ptr<int> sp3 = sp2; // sp2和sp3共享对象,引用计数=2 // 访问对象 cout << *sp2 << " " << *sp3 << endl; // 输出200 200 cout << "引用计数:" << sp2.use_count() << endl; // 输出2(sp2和sp3) // sp3销毁(超出作用域),引用计数-1 { shared_ptr<int> sp4 = sp2; cout << "sp4存在时计数:" << sp2.use_count() << endl; // 输出3 } // sp4销毁,计数变回2 // 重置sp2:sp2不再拥有对象,计数-1 sp2.reset(); cout << "sp2重置后计数:" << sp3.use_count() << endl; // 输出1(仅剩sp3) return 0; // sp1和sp3销毁,计数变为0,对象被释放 }
适用场景:
- 多个对象需要共享同一资源的所有权(如多个指针指向同一动态对象)。
- 复杂数据结构(如树、图)中,节点可能被多个父节点引用。
3. std::weak_ptr:弱引用(解决循环引用)
核心问题:
shared_ptr
可能导致循环引用(两个对象互相用shared_ptr
引用,导致引用计数永远不为 0,内存泄漏)。示例(循环引用问题):
struct Node { shared_ptr<Node> next; // 指向另一个节点 ~Node() { cout << "Node被释放" << endl; } }; int main() { shared_ptr<Node> n1 = make_shared<Node>(); shared_ptr<Node> n2 = make_shared<Node>(); n1->next = n2; // n1引用n2,n2计数=2 n2->next = n1; // n2引用n1,n1计数=2 // 函数结束时,n1和n2计数各减1,变为1(互相引用) // 导致Node对象无法释放,内存泄漏(析构函数不执行) return 0; }
weak_ptr 的作用:
- 对
shared_ptr
管理的对象进行弱引用(不增加引用计数)。- 可观察对象是否存在,但无法直接访问对象(需先通过
lock()
转为shared_ptr
)。解决循环引用示例:
struct Node { weak_ptr<Node> next; // 用weak_ptr替代shared_ptr ~Node() { cout << "Node被释放" << endl; } // 此时会正常执行 }; int main() { shared_ptr<Node> n1 = make_shared<Node>(); shared_ptr<Node> n2 = make_shared<Node>(); n1->next = n2; // weak_ptr不增加n2的计数(n2计数仍为1) n2->next = n1; // weak_ptr不增加n1的计数(n1计数仍为1) // 函数结束时,n1和n2计数减为0,对象被释放 return 0; }
weak_ptr 常用操作:
shared_ptr<int> sp = make_shared<int>(10); weak_ptr<int> wp = sp; // 从shared_ptr创建weak_ptr(不增加计数) // 检查对象是否存在 if (!wp.expired()) { // expired():对象已释放则返回true // 转为shared_ptr才能访问对象(此时计数+1,确保访问期间对象不被释放) shared_ptr<int> temp = wp.lock(); cout << *temp << endl; // 输出10 } // temp销毁,计数-1
适用场景:
- 解决
shared_ptr
的循环引用问题。- 缓存、观察者模式等场景(只观察对象,不影响其生命周期)。
三、智能指针的关键注意事项
1、优先使用
make_unique
和make_shared
- 避免直接用
new
初始化智能指针(如unique_ptr<int> p(new int(10))
),make_xxx
更安全(避免内存泄漏风险)且高效(make_shared
可一次性分配对象和控制块内存)。auto up = make_unique<int>(10); // 推荐 auto sp = make_shared<int>(20); // 推荐
2、不要用同一原始指针初始化多个智能指针
会导致重复释放内存:int* raw_ptr = new int(10); unique_ptr<int> p1(raw_ptr); unique_ptr<int> p2(raw_ptr); // 错误!p1和p2会分别释放raw_ptr,导致崩溃
3、不要管理非动态分配的内存
智能指针会自动调用delete
,若管理栈上内存或全局内存,会导致未定义行为:int x = 10; unique_ptr<int> p(&x); // 错误!x是栈内存,退出作用域时会被delete两次
4、自定义删除器(处理特殊资源)
智能指针默认用delete
释放内存,若管理其他资源(如文件句柄、网络连接),可指定自定义删除器:// 管理文件指针(需要用fclose释放) auto del = [](FILE* fp) { fclose(fp); }; // 自定义删除器(lambda) unique_ptr<FILE, decltype(del)> fp(fopen("test.txt", "r"), del);
四、三种智能指针的对比
智能指针 所有权 复制性 引用计数 适用场景 unique_ptr
独占 不可复制,可移动 无 单个对象的独占管理 shared_ptr
共享 可复制,可移动 有 多个对象共享同一资源 weak_ptr
无(弱引用) 可复制,可移动 无 解决 shared_ptr
循环引用
五、总结
智能指针是 C++ 内存管理的核心工具,通过 RAII 机制自动释放资源,避免手动管理的风险:
- 若资源只需一个所有者,用
unique_ptr
(高效、安全)。- 若资源需要多个所有者,用
shared_ptr
(配合weak_ptr
解决循环引用)。- 永远优先使用
make_unique
/make_shared
创建智能指针,避免直接操作原始指针。掌握智能指针是写出健壮 C++ 代码的关键,尤其在大型项目中能显著减少内存相关 bug。
更多推荐
所有评论(0)