1. 智能指针家族
- C++ 标准库提供了三种主要的智能指针,定义在
头文件中:
| 智能指针 | 所有权 | 引用计数 | 可拷贝 | 核心特点 |
|---|---|---|---|---|
| unique_ptr | 独占 | 无 | 不可拷贝,只能移动 | 零开销,性能与裸指针相当 |
| shared_ptr | 共享 | 有 | 可拷贝 | 引用计数管理,最后一个销毁时释放 |
| weak_ptr | 非拥有 | 有(弱计数) | 可拷贝 | 不增加引用计数,用于打破循环引用 |
2. 为什么需要智能指针
2.1 内存泄漏
- 函数中途 return 抛出异常,delete 没执行,堆内存永久占用
void test()
{
int* p = new int(10);
if(true) return; // 直接退出,delete 永远执行不到,内存泄漏
delete p;
}
2.2 重复释放
int* p = new int;
int* q = p;
delete p;
delete q; // 同一块内存释放两次,程序崩溃
2.3 野指针悬空
- 释放后没有置空,再次访问非法内存
int* p = new int;
delete p;
*p = 10; // 野指针,未定义行为
2.4 异常安全漏洞
-异常抛出跳过 delete,内存泄漏。
核心解决方案:RAII。资源获取即初始化:把堆资源托管给栈对象,栈对象生命周期结束自动调用析构释放内存,无需手动 delete。智能指针就是 RAII 的标准实现。
3. std::unique_ptr 独占智能指针
3.1 基础语法
#include<menory>
//方式1:new初始化,不推荐
unique_ptr<int>p1(new int(666));
//方式2: C++14 make_unique,推荐
auto p2 = make_unique<int>(667);
//空指针
unique_ptr<int>empty_p;
3.2 禁止拷贝,仅支持移动
unique_ptr<int> p1(new int(100));
unique_ptr<int> p2 = p1; // 编译报错,禁止拷贝
unique_ptr<int> p3 = move(p1); // std::move转移所有权,p1变空
3.3 常用接口
p.get(); // 返回裸指针,谨慎使用
p.release(); // 释放所有权,返回裸指针,unique_ptr置空
p.reset(); // 释放当前资源,置空
p.reset(new int(20)); // 释放旧资源,托管新内存
bool flag = p; // 判断是否持有有效资源
*p; p->func(); // 解引用、访问成员,和裸指针用法一致
3.4 支持数组管理
unique_ptr<int[]> arr = make_unique<int[]>(10);
arr[0] = 10; // 数组下标访问,析构自动 delete[]
4. std::shared_ptr 共享智能指针
- 多个 shared_ptr 可以托管同一块堆内存,内部维护原子引用计数器:
- 拷贝一份,计数 + 1
- 析构 /reset,计数 - 1
- 计数归 0 时,自动释放堆内存
4.1 基础用法
// C++11 推荐 make_shared,内存分配一次(对象+计数器同一块内存)
auto p1 = make_shared<int>(999);
shared_ptr<int> p2 = p1; // 拷贝,计数+1,现在计数=2
cout << p1.use_count(); // 获取当前引用计数
4.2 拷贝、移动行为
auto p1 = make_shared<int>(10);
auto p2 = p1; // 拷贝:计数+1
auto p3 = move(p1); // 移动:p1置空,计数不变
4.3 自定义删除器(解决数组、特殊资源)
// 管理数组
shared_ptr<int> arr(new int[10], [](int* p){ delete[] p; });
4.4 缺陷
- 两个对象互相持有对方的 shared_ptr,引用计数永远无法归 0,内存永远不释放。
struct Node
{
int val;
shared_ptr<Node> next;
Node(int v):val(v){}
};
void loop_ref()
{
auto n1 = make_shared<Node>(1);
auto n2 = make_shared<Node>(2);
n1->next = n2;
n2->next = n1;
// 函数结束,n1、n2析构,计数各减1,但互相持有,计数=1,内存泄漏!
}
解决方案:搭配 weak_ptr 打破循环引用。
4.5 性能开销
- 引用计数是原子操作,多线程下有轻微性能损耗
- make_shared 一次分配内存,比 shared_ptr
(new) 更高效
5. std::weak_ptr 弱智能指针(配套 shared_ptr)
- 不增加引用计数,仅作为 shared_ptr 的观察者,解决循环引用。
- 不能单独管理资源,必须依附 shared_ptr。
5.1 接口
expired():判断指向的内存是否已经释放(计数 = 0)lock():提升为 shared_ptr,若资源已销毁返回空 shared_ptruse_count():查看关联的 shared_ptr 计数- 解决循环引用完整示例
#include <memory>
struct Node
{
int val;
weak_ptr<Node> next; // 改用weak_ptr,不增加计数
Node(int v):val(v){}
};
void fix_loop()
{
auto n1 = make_shared<Node>(1);
auto n2 = make_shared<Node>(2);
n1->next = n2; // weak_ptr,计数不变
n2->next = n1;
// 函数结束,计数直接归0,内存正常释放
}
- weak_ptr 不能直接解引用,必须 lock () 转为 shared_ptr 再使用:
auto sp = make_shared<int>(10);
weak_ptr<int> wp = sp;
if(!wp.expired())
{
auto temp = wp.lock();
cout << *temp;
}
浙公网安备 33010602011771号