RAII | 智能指针
RAII思想 ,智能指针,shared_ptr,unique_ptr,weak_ptr讲解与实现
RAII
1.1 核心定义
RAII(Resource Acquisition Is Initialization)即 “资源获取即初始化”,是一种利用对象生命周期管理程序资源(内存、文件句柄、锁等)的技术。
1.2 核心原理
- 资源在对象构造时获取(如 new 内存、加锁);
- 资源在对象生命周期内始终有效(由对象管理,避免误释放);
- 资源在对象析构时自动释放(析构函数是 C++ 保证会执行的代码块,即使抛异常也会调用)。
1.3 核心优势
- 无需显式释放资源,避免 “忘记释放” 导致的泄漏;
- 解决异常安全问题:即使程序抛异常,对象析构仍会执行,资源不会泄漏;
- 资源管理集中化,代码更简洁、健壮。
示例:RAII 解决异常导致的内存泄漏
#include <iostream>
#include <stdexcept>
using namespace std;
// RAII思想的简单资源管理类
template<class T>
class RAIIResource {
public:
// 构造时获取资源(new内存)
RAIIResource(T* ptr = nullptr) : _ptr(ptr) {}
// 析构时释放资源(delete内存)
~RAIIResource() {
if (_ptr) {
cout << "释放资源:" << _ptr << endl;
delete _ptr;
_ptr = nullptr;
}
}
private:
T* _ptr;
};
void Func() {
RAIIResource<int> res(new int(10)); // 构造时获取资源
throw invalid_argument("测试异常"); // 抛异常,函数终止
// 无需显式delete,析构会自动释放资源
}
int main() {
try {
Func();
} catch (const exception& e) {
cout << "捕获异常:" << e.what() << endl;
}
return 0;
}
运行结果:
释放资源:0x55f8d7a7c2a0
捕获异常:测试异常
即使抛异常,资源仍被正常释放,避免内存泄漏。
unique_ptr 深度解析
核心定位
std::unique_ptr 是 C++11 引入的独占式智能指针,核心设计目标是:
- 保证「同一时间只有一个
unique_ptr拥有对堆资源的所有权」;
构造与初始化方式
unique_ptr 的构造核心是「接管堆资源」,且仅支持移动语义(禁用拷贝),以下是所有常用构造 / 初始化方式,按优先级排序:
make_unique 构造(C++14 及以上)
std::make_unique 是创建 unique_ptr 的最优方式:
- 更安全:避免裸指针多次构造导致的重复释放;
- 更高效:减少一次内存分配(直接在堆上创建对象并绑定);
- 语法简洁:无需手动写
new。
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;
struct Date {
int year = 2024, month = 5, day = 20;
Date(int y, int m, int d) : year(y), month(m), day(d) {}
~Date() { cout << "Date析构:" << this << endl; }
};
int main() {
// 1. 构造普通类型
unique_ptr<int> up1 = make_unique<int>(10);
cout << "*up1 = " << *up1 << endl; // 10
// 2. 构造自定义类型(带参数)
unique_ptr<Date> up2 = make_unique<Date>(2024, 5, 20);
cout << up2->year << endl; // 2024
// 3. 构造数组(自动适配数组删除器)
unique_ptr<int[]> up3 = make_unique<int[]>(5); // 长度为5的int数组
for (int i = 0; i < 5; ++i) {
up3[i] = i * 2; // 支持数组下标访问
cout << up3[i] << " "; // 0 2 4 6 8
}
return 0;
}
基础方式:裸指针构造(不推荐,但需了解)
直接用 new 出来的裸指针构造,需注意:禁止用同一个裸指针构造多个 unique_ptr(会导致重复释放)。
int main() {
// 1. 普通类型构造
unique_ptr<int> up1(new int(100));
// 2. 自定义类型构造
unique_ptr<Date> up2(new Date(2025, 1, 1));
// 3. 数组构造(必须用[]特化,否则析构时会用delete而非delete[])
unique_ptr<int[]> up3(new int[3]{1,2,3});
// ❌ 错误:同一裸指针构造多个unique_ptr,析构时重复释放
// int* raw = new int(10);
// unique_ptr<int> up4(raw);
// unique_ptr<int> up5(raw);
return 0;
}
移动构造 / 赋值(转移所有权)
unique_ptr 禁用拷贝,但支持移动(move)—— 通过 std::move 转移资源所有权,原指针会被置空。
int main() {
unique_ptr<int> up1 = make_unique<int>(10);
cout << "up1是否为空:" << (up1 == nullptr) << endl; // 0(false)
// 1. 移动构造
unique_ptr<int> up2 = move(up1);
cout << "up1是否为空:" << (up1 == nullptr) << endl; // 1(true)
cout << "*up2 = " << *up2 << endl; // 10
// 2. 移动赋值
unique_ptr<int> up3 = make_unique<int>(20);
up3 = move(up2); // up3释放原有资源(int=20),接管up2的资源(int=10)
cout << "*up3 = " << *up3 << endl; // 10
return 0;
}
结果如下
up1是否为空:0
up1是否为空:1
*up2 = 10
*up3 = 10
基础方式:默认构造(空指针)
构造一个不管理任何资源的空 unique_ptr,可后续通过 reset 接管资源:
int main() {
unique_ptr<int> up; // 默认构造,空指针
cout << "up是否为空:" << (up == nullptr) << endl; // 1(true)
up.reset(new int(10)); // 后续接管资源
cout << "*up = " << *up << endl; // 10
return 0;
}
unique_ptr 对资源的管理方式
unique_ptr 基于 RAII(资源获取即初始化) 管理资源,核心规则如下:
所有权规则(核心)
- 独占性:一个堆资源只能被一个
unique_ptr拥有; - 移动转移:所有权仅能通过
std::move转移,转移后原unique_ptr变为空,不再管理任何资源; - 禁止拷贝:直接拷贝
unique_ptr会编译报错(删除了拷贝构造和拷贝赋值运算符)。
释放规则
- 析构自动释放:当
unique_ptr生命周期结束(如出作用域、被销毁),会自动调用析构函数释放管理的资源; - 主动释放 / 转移:可通过
release/reset主动控制资源的释放或转移; - 异常安全:即使程序抛出异常,
unique_ptr的析构函数仍会执行,确保资源不泄漏。
核心成员函数
| 函数 | 作用 | 用法示例 |
|---|---|---|
release() |
释放资源所有权(返回裸指针),但不释放资源(需手动 delete) | int* raw = up.release(); delete raw; |
reset(ptr = nullptr) |
释放当前管理的资源(若有),可选接管新资源 ptr |
up.reset(new int(20));(释放旧资源,接管新资源)up.reset();(仅释放旧资源) |
swap(up) |
交换两个 unique_ptr 的资源所有权 |
up1.swap(up2); |
get() |
获取管理的裸指针(仅观察,不释放所有权) | int* raw = up.get(); cout << *raw; |
get_deleter() |
获取自定义删除器(仅带删除器的 unique_ptr 可用) |
auto del = up_file.get_deleter(); del(fp); |
operator* |
解引用,访问资源本体(仅非数组版可用) | cout << *up; |
operator-> |
访问资源的成员(仅非数组版可用) | cout << up->year; |
operator[] |
数组下标访问(仅数组版 unique_ptr<T[]> 可用) |
up[0] = 10; |
operator bool |
判断是否管理有效资源(非空) | if (up) { cout << "非空"; } |
unique_ptr 手动实现
#include <iostream>
#include <utility> // std::move 头文件
using namespace std;
template<class T>
class UniquePtr {
public:
// 1. 默认构造:空指针
UniquePtr() : _ptr(nullptr) {}
// 2. 裸指针构造:接管堆资源(explicit避免隐式转换)
explicit UniquePtr(T* ptr) : _ptr(ptr) {
cout << "UniquePtr构造:接管资源 " << _ptr << endl;
}
// 3. 析构:自动释放资源(RAII核心)
~UniquePtr() {
if (_ptr) {
cout << "UniquePtr析构:释放资源 " << _ptr << endl;
delete _ptr; // 数组版需重载为delete[],此处简化
_ptr = nullptr;
}
}
// 4. 移动构造:转移所有权(禁用拷贝构造)
UniquePtr(UniquePtr&& up) noexcept : _ptr(up._ptr) {
up._ptr = nullptr; // 原指针置空,避免二次释放
cout << "UniquePtr移动构造:转移资源" << endl;
}
// 5. 移动赋值:转移所有权(禁用拷贝赋值)
UniquePtr& operator=(UniquePtr&& up) noexcept {
if (this != &up) { // 避免自赋值
// 第一步:释放当前资源
if (_ptr) {
delete _ptr;
cout << "UniquePtr移动赋值:释放旧资源 " << _ptr << endl;
}
// 第二步:转移所有权
_ptr = up._ptr;
up._ptr = nullptr;
cout << "UniquePtr移动赋值:接管新资源 " << _ptr << endl;
}
return *this;
}
// 6. 禁用拷贝构造和拷贝赋值(核心:保证独占性)
UniquePtr(const UniquePtr&) = delete;
UniquePtr& operator=(const UniquePtr&) = delete;
// 7. 指针行为:重载*和->
T& operator*() const {
return *_ptr;
}
T* operator->() const {
return _ptr;
}
// 9. 重载bool:判空
explicit operator bool() const {
return _ptr != nullptr;
}
private:
T* _ptr; // 管理的堆资源指针
};
// 测试代码
int main() {
// 构造
UniquePtr<int> up1(new int(10));
cout << "*up1 = " << *up1 << endl;
// 移动构造
UniquePtr<int> up2 = move(up1);
if (!up1) cout << "up1已为空" << endl;
cout << "*up2 = " << *up2 << endl;
// 移动赋值
UniquePtr<int> up3(new int(20));
up3 = move(up2);
cout << "*up3 = " << *up3 << endl;
return 0;
}
运行结果
UniquePtr构造:接管资源 0000011DCAEC9C10
*up1 = 10
UniquePtr移动构造:转移资源
up1已为空
*up2 = 10
UniquePtr构造:接管资源 0000011DCAECA410
UniquePtr移动赋值:释放旧资源 0000011DCAECA410
UniquePtr移动赋值:接管新资源 0000011DCAEC9C10
*up3 = 10
UniquePtr析构:释放资源 0000011DCAEC9C10
unique_ptr 优缺点
| 优点 | 场景说明 |
|---|---|
| 极致高效 | 无引用计数、无锁,性能和裸指针几乎一致,适合高性能场景; |
| 绝对安全 | 禁用拷贝,从语法上避免浅拷贝、重复释放等经典内存错误; |
| 异常安全 | RAII 保证析构释放资源,解决「异常导致资源泄漏」问题; |
| 用法灵活 | 支持自定义删除器、数组特化,可管理各种堆资源甚至非内存资源; |
| 兼容性好 | 可隐式转换为 shared_ptr,满足「独占→共享」的场景切换; |
| 语义清晰 | 「独占」语义明确,代码可读性高,一眼能看出资源的所有权规则; |
| 缺点 | 解决方案 |
|---|---|
| 不支持拷贝共享 | 需共享资源时,改用 shared_ptr; |
| 移动后原指针失效 | 移动后避免使用原指针(可通过 operator bool 判空); |
| 裸指针构造有风险 | 优先用 make_unique,避免同一个裸指针构造多个 unique_ptr; |
数组版不支持 operator*/operator-> |
数组版用 operator[] 访问元素,符合数组使用习惯; |
| 自定义删除器会增加体积 | 普通场景用默认删除器,特殊场景才自定义,平衡灵活性和体积; |
std::shared_ptr 深度解析
std::shared_ptr 是 C++11 推出的共享式智能指针,核心设计目标是解决 unique_ptr 无法共享资源的问题 —— 通过「引用计数」跟踪资源的被引用次数,允许多个 shared_ptr 共同管理同一堆资源,仅当最后一个引用者销毁时,才释放资源。
shared_ptr 核心定位
- 核心机制:基于 RAII + 引用计数(堆上存储计数,保证多个
shared_ptr共享同一计数); - 所有权规则:多个
shared_ptr共享资源所有权,计数≥1 时资源有效,计数 = 0 时释放资源; - 适用场景:需要多对象、多模块共享同一资源的场景(如容器存储指针、多线程共享对象);
- 配套工具:需配合
weak_ptr解决「循环引用」问题(shared_ptr最大坑点)。
构造与初始化方式
推荐方式:make_shared 构造(C++11 及以上)
std::make_shared 是创建 shared_ptr 的最优方式:
- 更高效:仅分配一次内存(同时存储对象和引用计数,普通构造需分配两次:对象 + 计数);
- 更安全:避免裸指针多次构造导致的重复释放;
- 异常安全:不会因中间步骤抛异常导致内存泄漏。
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;
struct Date {
int year = 2024, month = 5, day = 20;
Date(int y, int m, int d) : year(y), month(m), day(d) {
cout << "Date构造:" << this << endl;
}
~Date() {
cout << "Date析构:" << this << endl;
}
};
int main() {
// 1. 构造普通类型
shared_ptr<int> sp1 = make_shared<int>(10);
cout << "*sp1 = " << *sp1 << ",计数:" << sp1.use_count() << endl; // 10,计数1
// 2. 构造自定义类型(带参数)
shared_ptr<Date> sp2 = make_shared<Date>(2024, 5, 20);
cout << sp2->year << ",计数:" << sp2.use_count() << endl; // 2024,计数1
return 0;
}
基础方式:裸指针构造
直接用 new 出来的裸指针构造,禁止用同一个裸指针构造多个 shared_ptr(会导致重复释放):
int main() {
// 1. 普通类型构造
shared_ptr<int> sp1(new int(100));
// 2. 自定义类型构造
shared_ptr<Date> sp2(new Date(2025, 1, 1));
// ❌ 致命错误:同一裸指针构造多个shared_ptr,析构时重复释放
// int* raw = new int(10);
// shared_ptr<int> sp3(raw);
// shared_ptr<int> sp4(raw);
return 0;
}
核心方式:拷贝构造 / 赋值(共享资源,计数 + 1)
shared_ptr 支持拷贝(区别于 unique_ptr),拷贝后多个指针共享同一资源,引用计数递增:
int main() {
shared_ptr<int> sp1 = make_shared<int>(10);
cout << "sp1计数:" << sp1.use_count() << endl; // 1
// 1. 拷贝构造
shared_ptr<int> sp2 = sp1;
cout << "sp1计数:" << sp1.use_count() << endl; // 2
cout << "sp2计数:" << sp2.use_count() << endl; // 2
// 2. 拷贝赋值
shared_ptr<int> sp3 = make_shared<int>(20);
sp3 = sp1; // sp3释放原有资源(int=20),共享sp1的资源,计数+1
cout << "sp1计数:" << sp1.use_count() << endl; // 3
return 0;
}
sp1计数:1
sp1计数:2
sp2计数:2
sp1计数:3
2.4 特殊方式:移动构造 / 赋值(转移资源,计数不变)
移动构造 / 赋值不会增加计数,而是转移原指针的资源所有权,原指针变为空:
int main() {
shared_ptr<int> sp1 = make_shared<int>(10);
cout << "sp1计数:" << sp1.use_count() << endl; // 1
// 1. 移动构造
shared_ptr<int> sp2 = move(sp1);
cout << "sp1是否为空:" << (sp1 == nullptr) << endl; // 1(true)
cout << "sp2计数:" << sp2.use_count() << endl; // 1(计数不变)
// 2. 移动赋值
shared_ptr<int> sp3 = make_shared<int>(20);
sp3 = move(sp2);
cout << "sp3计数:" << sp3.use_count() << endl; // 1
return 0;
}
2.6 基础方式:默认构造(空指针)
构造一个不管理任何资源的空 shared_ptr,可后续通过 reset 接管资源:
int main() {
shared_ptr<int> sp; // 默认构造,空指针
cout << "sp计数:" << sp.use_count() << endl; // 0
sp.reset(new int(10)); // 后续接管资源
cout << "*sp = " << *sp << ",计数:" << sp.use_count() << endl; // 10,计数1
return 0;
}
shared_ptr 对资源的管理方式
shared_ptr 基于「RAII + 引用计数」管理资源,核心规则如下:
引用计数的存储与更新规则
- 计数存储:引用计数存储在堆上(而非
shared_ptr对象内),保证多个shared_ptr共享同一计数; - 计数递增:拷贝构造 / 赋值时,计数原子性 + 1(线程安全);
- 计数递减:析构 /
reset时,计数原子性 - 1(线程安全); - 释放规则:当计数减至 0 时,自动调用删除器释放管理的资源,同时释放计数本身。
线程安全规则
shared_ptr 的线程安全分为两部分:
- ✅ 引用计数线程安全:计数的增减操作是原子的(内部加锁),多线程拷贝 / 析构
shared_ptr不会导致计数混乱; - ❌ 资源访问线程不安全:多个线程同时修改
shared_ptr管理的资源(如*sp = 100),需手动加锁; - ❌
shared_ptr对象本身线程不安全:多线程同时修改同一个shared_ptr对象(如sp = make_shared<int>(10)),需手动加锁。
示例:线程安全验证
#include <thread>
#include <mutex>
shared_ptr<int> sp = make_shared<int>(10);
mutex mtx; // 保护资源访问的锁
void ThreadFunc() {
// 1. 拷贝shared_ptr:计数+1(线程安全)
shared_ptr<int> local_sp = sp;
cout << "线程内计数:" << local_sp.use_count() << endl;
// 2. 修改资源:需加锁(否则数据竞争)
lock_guard<mutex> lock(mtx);
*local_sp += 1;
}
int main() {
thread t1(ThreadFunc);
thread t2(ThreadFunc);
t1.join();
t2.join();
cout << "最终值:" << *sp << endl; // 12(两个线程各加1)
return 0;
}
循环引用问题(最大坑点)
当两个 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;
n2->next = n1;
// n1和n2析构时,计数各为1,无法释放资源
return 0;
}
解决方案:用 weak_ptr 替代一方的 shared_ptr(weak_ptr 不增加计数)。
核心成员函数
| 函数 | 作用 | 用法示例 |
|---|---|---|
use_count() |
获取当前引用计数(仅用于调试,不保证实时性) | cout << sp.use_count(); |
reset(ptr = nullptr) |
释放当前资源所有权(计数 - 1),可选接管新资源 ptr |
sp.reset(new int(20));(释放旧资源,接管新资源)sp.reset();(仅释放旧资源) |
swap(sp) |
交换两个 shared_ptr 的资源和引用计数 |
sp1.swap(sp2); |
get() |
获取管理的裸指针(仅观察,不影响计数) | int* raw = sp.get(); cout << *raw; |
unique() |
判断是否独占资源(计数 ==1,C++20 已废弃,改用 use_count()==1) |
if (sp.unique()) { cout << "独占资源"; } |
operator* |
解引用,访问资源本体 | cout << *sp; |
operator-> |
访问资源的成员 | cout << sp->year; |
operator bool |
判断是否管理有效资源(非空) | if (sp) { cout << "非空"; } |
get_deleter() |
获取自定义删除器(仅带删除器的 shared_ptr 可用) |
auto del = sp_file.get_deleter(); del(fp); |
核心函数实操示例
int main() {
shared_ptr<int> sp = make_shared<int>(10);
// 1. use_count():查看计数
cout << "初始计数:" << sp.use_count() << endl; // 1
// 2. reset():释放旧资源,接管新资源
sp.reset(new int(20));
cout << "*sp = " << *sp << ",计数:" << sp.use_count() << endl; // 20,计数1
// 3. swap():交换资源
shared_ptr<int> sp2 = make_shared<int>(30);
sp.swap(sp2);
cout << "*sp = " << *sp << endl; // 30
cout << "*sp2 = " << *sp2 << endl; // 20
// 4. get():获取裸指针
int* raw = sp.get();
cout << "*raw = " << *raw << endl; // 30
// 5. operator bool:判空
if (sp) {
cout << "sp管理有效资源" << endl;
}
return 0;
}
shared_ptr 手动实现
#include <iostream>
#include <mutex>
#include <utility> // std::move
using namespace std;
template<class T>
class SharedPtr {
public:
// 1. 默认构造:空指针
SharedPtr() : _ptr(nullptr), _refCount(nullptr), _mtx(nullptr) {}
// 2. 裸指针构造:接管资源,初始化计数为1
explicit SharedPtr(T* ptr)
: _ptr(ptr),
_refCount(ptr ? new int(1) : nullptr),
_mtx(ptr ? new mutex : nullptr) {
cout << "SharedPtr构造:接管资源 " << _ptr << ",计数:" << use_count() << endl;
}
// 3. 拷贝构造:共享资源,计数+1(线程安全)
SharedPtr(const SharedPtr& sp)
: _ptr(sp._ptr), _refCount(sp._refCount), _mtx(sp._mtx) {
if (_refCount) {
// 加锁保证计数原子性
_mtx->lock();
++(*_refCount);
_mtx->unlock();
}
cout << "SharedPtr拷贝构造:计数=" << use_count() << endl;
}
// 4. 赋值重载:释放旧资源,共享新资源
SharedPtr& operator=(const SharedPtr& sp) {
if (this != &sp) { // 避免自赋值
// 第一步:释放当前资源
Release();
// 第二步:共享新资源
_ptr = sp._ptr;
_refCount = sp._refCount;
_mtx = sp._mtx;
if (_refCount) {
_mtx->lock();
++(*_refCount);
_mtx->unlock();
}
cout << "SharedPtr赋值:计数=" << use_count() << endl;
}
return *this;
}
// 5. 析构:计数-1,为0时释放资源
~SharedPtr() {
Release();
}
// 6. 指针行为:重载*和->
T& operator*() const {
return *_ptr;
}
T* operator->() const {
return _ptr;
}
// 7. 核心成员函数
int use_count() const {
return _refCount ? *_refCount : 0;
}
void reset(T* ptr = nullptr) {
Release(); // 释放当前资源
// 接管新资源
_ptr = ptr;
_refCount = ptr ? new int(1) : nullptr;
_mtx = ptr ? new mutex : nullptr;
cout << "SharedPtr::reset():接管新资源 " << _ptr << ",计数=" << use_count() << endl;
}
T* get() const {
return _ptr;
}
explicit operator bool() const {
return _ptr != nullptr;
}
private:
// 核心:释放资源逻辑
void Release() {
if (_refCount) {
_mtx->lock();
bool needDelete = false;
if (--(*_refCount) == 0) {
// 计数为0,释放资源、计数、锁
delete _ptr;
delete _refCount;
needDelete = true;
cout << "SharedPtr::Release():释放资源 " << _ptr << endl;
}
_mtx->unlock();
if (needDelete) delete _mtx;
// 置空,避免野指针
_ptr = nullptr;
_refCount = nullptr;
_mtx = nullptr;
}
}
private:
T* _ptr; // 管理的堆资源指针
int* _refCount; // 堆上的引用计数(共享计数)
mutex* _mtx; // 互斥锁(保证计数线程安全)
};
// 测试代码
int main() {
// 构造
SharedPtr<int> sp1(new int(10));
cout << "*sp1 = " << *sp1 << endl;
// 拷贝构造
SharedPtr<int> sp2 = sp1;
cout << "sp1计数:" << sp1.use_count() << endl; // 2
// 赋值重载
SharedPtr<int> sp3(new int(20));
sp3 = sp1;
cout << "sp3计数:" << sp3.use_count() << endl; // 3
// reset
sp3.reset(new int(30));
cout << "*sp3 = " << *sp3 << endl; // 30
return 0;
}
运行结果(体现核心逻辑)
SharedPtr构造:接管资源 0x55d7a9e2c2a0,计数:1
*sp1 = 10
SharedPtr拷贝构造:计数=2
sp1计数:2
SharedPtr构造:接管资源 0x55d7a9e2c2c0,计数:1
SharedPtr::Release():释放资源 0x55d7a9e2c2c0
SharedPtr赋值:计数=3
sp3计数:3
SharedPtr::Release():释放资源 0x55d7a9e2c2a0
SharedPtr::reset():接管新资源 0x55d7a9e2c2e0,计数=1
*sp3 = 30
SharedPtr::Release():释放资源 0x55d7a9e2c2e0
shared_ptr 优缺点
| 优点 | 场景说明 |
|---|---|
| 支持资源共享 | 适配多对象、多模块共享资源的场景(如容器存储指针、多线程共享对象); |
| 引用计数线程安全 | 多线程拷贝 / 析构 shared_ptr 不会导致计数混乱,无需手动加锁; |
| 异常安全 | RAII 保证析构释放资源,解决「异常导致资源泄漏」问题; |
| 用法灵活 | 支持自定义删除器,可管理各种堆资源甚至非内存资源; |
| 可转换性 | 可接收 unique_ptr 的移动构造(unique_ptr → shared_ptr); |
| 语义清晰 | 「共享」语义明确,代码可读性高,一眼能看出资源的共享规则; |
| 缺点 | 解决方案 |
|---|---|
| 性能开销 | 计数增减需加锁(原子操作),比 unique_ptr 慢;优先用 unique_ptr,需共享时再用 shared_ptr; |
| 循环引用 | 用 weak_ptr 替代一方的 shared_ptr(如链表节点的互相引用); |
| 裸指针构造有风险 | 优先用 make_shared,避免同一个裸指针构造多个 shared_ptr; |
| make_shared 限制 | make_shared 无法自定义删除器;需自定义删除器时,用裸指针构造; |
| 内存碎片 | make_shared 一次性分配对象 + 计数,析构时计数释放但对象内存可能延迟释放(取决于内存分配器); |
| 资源访问不安全 | 多线程修改资源时,手动加锁(如 std::mutex); |
std::weak_ptr
std::weak_ptr 是 C++11 推出的弱引用智能指针,它并非独立的资源管理工具,而是 std::shared_ptr 的「辅助工具」—— 核心目标是解决 shared_ptr 的循环引用问题,同时保证对资源的安全观察。
weak_ptr 核心定位
- 核心角色:
shared_ptr的「观察者」,无资源所有权,仅观察shared_ptr管理的资源; - 核心机制:不增加
shared_ptr的引用计数,仅存储资源指针和引用计数指针,用于判断资源是否有效; - 核心价值:打破
shared_ptr的循环引用(如链表 / 树节点互相引用),避免资源泄漏; - 使用限制:不能直接访问资源,必须通过
lock()转为shared_ptr后才能安全访问; - 适用场景:需观察共享资源但不参与其生命周期管理的场景(如缓存、观察者模式)。
简单说:weak_ptr 是 shared_ptr 的「旁观者」—— 只看不用,不影响资源的生老病死。
weak_ptr 构造与初始化方式
weak_ptr 必须关联 shared_ptr 才能使用,其构造 / 初始化核心是「关联已有的 shared_ptr 或 weak_ptr」,且全程不增加引用计数:
基础方式:默认构造(空 weak_ptr)
构造一个不观察任何资源的空 weak_ptr,可后续通过赋值关联 shared_ptr:
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;
struct Date {
int year = 2024, month = 5, day = 20;
~Date() { cout << "Date析构:" << this << endl; }
};
int main() {
// 默认构造:空weak_ptr,不观察任何资源
weak_ptr<Date> wp1;
cout << "wp1是否过期:" << wp1.expired() << endl; // 1(true,无资源可观察)
cout << "wp1引用计数:" << wp1.use_count() << endl; // 0
// 后续关联shared_ptr
shared_ptr<Date> sp = make_shared<Date>();
wp1 = sp; // 赋值关联,不增加sp的计数
cout << "sp计数:" << sp.use_count() << endl; // 1(wp1未增加计数)
return 0;
}
2.2 核心方式:从 shared_ptr 构造 / 赋值
这是 weak_ptr 最常用的初始化方式,直接关联 shared_ptr,但不修改其引用计数:
int main() {
// 1. 从shared_ptr构造
shared_ptr<int> sp1 = make_shared<int>(10);
weak_ptr<int> wp1(sp1); // 关联sp1,不增加计数
cout << "sp1计数:" << sp1.use_count() << endl; // 1
// 2. 从shared_ptr赋值
weak_ptr<int> wp2;
wp2 = sp1; // 关联sp1,仍不增加计数
cout << "sp1计数:" << sp1.use_count() << endl; // 1
return 0;
}
2.3 普通方式:从 weak_ptr 拷贝 / 赋值
拷贝 / 赋值其他 weak_ptr,关联同一资源,仍不增加引用计数:
int main() {
shared_ptr<int> sp = make_shared<int>(10);
weak_ptr<int> wp1(sp);
// 1. 拷贝构造
weak_ptr<int> wp2(wp1); // 关联同一资源,计数仍为1
cout << "sp计数:" << sp.use_count() << endl; // 1
// 2. 拷贝赋值
weak_ptr<int> wp3;
wp3 = wp2; // 关联同一资源,计数仍为1
cout << "sp计数:" << sp.use_count() << endl; // 1
return 0;
}
2.4 特殊方式:移动构造 / 赋值
移动构造 / 赋值转移 weak_ptr 的「观察权」,原 weak_ptr 变为空,不影响引用计数:
int main() {
shared_ptr<int> sp = make_shared<int>(10);
weak_ptr<int> wp1(sp);
// 1. 移动构造
weak_ptr<int> wp2 = move(wp1);
cout << "wp1是否过期:" << wp1.expired() << endl; // 1(空)
cout << "wp2引用计数:" << wp2.use_count() << endl; // 1
// 2. 移动赋值
weak_ptr<int> wp3;
wp3 = move(wp2);
cout << "wp2是否过期:" << wp2.expired() << endl; // 1(空)
cout << "wp3引用计数:" << wp3.use_count() << endl; // 1
return 0;
}
2.5 进阶方式:空指针构造(C++17 及以上)
直接构造观察空指针的 weak_ptr,等价于默认构造:
int main() {
weak_ptr<int> wp(nullptr); // C++17支持
cout << "wp是否过期:" << wp.expired() << endl; // 1(true)
return 0;
}
weak_ptr 对资源的管理方式
weak_ptr 不参与资源的生命周期管理,仅「观察」资源状态,核心规则如下:
3.1 弱引用规则
- 无所有权:
weak_ptr不拥有资源,仅存储「资源指针」和「shared_ptr 的引用计数指针」; - 不修改计数:构造 / 拷贝 / 赋值
weak_ptr时,不会增减shared_ptr的引用计数; - 资源释放:资源的释放完全由
shared_ptr的引用计数决定(计数 = 0 时释放),weak_ptr无干预权; - 悬垂保护:资源释放后,
weak_ptr不会自动置空,但可通过expired()判断资源是否有效。
3.2 循环引用解决
shared_ptr 互相引用时,计数无法归 0 导致资源泄漏,weak_ptr 可打破这一循环:
// 循环引用问题(shared_ptr版:资源泄漏)
struct NodeBad {
shared_ptr<NodeBad> next; // 互相引用,计数无法归0
~NodeBad() { cout << "NodeBad析构" << endl; } // 不会执行!
};
// 循环引用解决(weak_ptr版:正常释放)
struct NodeGood {
weak_ptr<NodeGood> next; // 弱引用,不增加计数
~NodeGood() { cout << "NodeGood析构" << endl; } // 正常执行
};
int main() {
// 1. 循环引用泄漏
shared_ptr<NodeBad> n1 = make_shared<NodeBad>();
shared_ptr<NodeBad> n2 = make_shared<NodeBad>();
n1->next = n2;
n2->next = n1;
// n1/n2析构时,计数各为1,资源泄漏
// 2. 循环引用解决
shared_ptr<NodeGood> m1 = make_shared<NodeGood>();
shared_ptr<NodeGood> m2 = make_shared<NodeGood>();
m1->next = m2;
m2->next = m1;
// m1/m2析构时,计数归0,资源释放
return 0;
}
运行结果:
NodeGood析构
NodeGood析构
3.3 安全访问规则
weak_ptr 不能直接解引用(无 operator*/operator->),必须通过 lock() 转为 shared_ptr 后访问:
- 若资源有效(计数 > 0):
lock()返回指向该资源的shared_ptr(计数 + 1),可安全访问; - 若资源已释放(计数 = 0):
lock()返回空shared_ptr,避免访问悬垂指针。
示例:安全访问资源
int main() {
weak_ptr<int> wp;
{
shared_ptr<int> sp = make_shared<int>(100);
wp = sp;
// 资源有效时lock()
shared_ptr<int> sp_tmp = wp.lock();
if (sp_tmp) { // 判空后访问
cout << "*sp_tmp = " << *sp_tmp << endl; // 100
}
} // sp析构,计数归0,资源释放
// 资源已释放时lock()
shared_ptr<int> sp_null = wp.lock();
if (!sp_null) {
cout << "资源已释放,无法访问" << endl;
}
return 0;
}
运行结果:
*sp_tmp = 100
资源已释放,无法访问
3.4 线程安全规则
weak_ptr 的线程安全与 shared_ptr 一致:
- ✅ 观察计数线程安全:
expired()/use_count()读取计数是原子操作,多线程观察无问题; - ❌ 资源访问线程不安全:
lock()得到shared_ptr后,修改资源仍需手动加锁; - ❌ weak_ptr 对象修改不安全:多线程同时修改同一个
weak_ptr(如wp = sp),需手动加锁。
核心成员函数
| 函数 | 作用 | 用法示例 |
|---|---|---|
lock() |
转为 shared_ptr:资源有效则返回非空 shared_ptr(计数 + 1),否则返回空 |
shared_ptr<int> sp = wp.lock(); if (sp) cout << *sp; |
expired() |
判断资源是否已释放(计数 ==0),返回 bool(true = 过期 / 释放) | if (wp.expired()) cout << "资源已释放"; |
reset() |
清空 weak_ptr,不再观察任何资源 |
wp.reset(); cout << wp.expired(); // 1 |
use_count() |
获取关联资源的引用计数(同 shared_ptr 的计数,仅用于调试) |
cout << wp.use_count(); |
operator= |
赋值关联 shared_ptr/weak_ptr,不增加计数 |
wp = sp; / wp = wp2; |
swap(wp) |
交换两个 weak_ptr 的观察目标 |
wp1.swap(wp2); |
核心函数实操示例
int main() {
shared_ptr<Date> sp = make_shared<Date>(2024, 5, 20);
weak_ptr<Date> wp(sp);
// 1. use_count():查看关联资源的计数
cout << "关联计数:" << wp.use_count() << endl; // 1
// 2. expired():判断资源是否有效
cout << "资源是否过期:" << wp.expired() << endl; // 0(false)
// 3. lock():安全访问资源
shared_ptr<Date> sp_tmp = wp.lock();
if (sp_tmp) {
cout << "日期:" << sp_tmp->year << "-" << sp_tmp->month << endl; // 2024-5
}
// 4. reset():清空weak_ptr
wp.reset();
cout << "reset后是否过期:" << wp.expired() << endl; // 1(true)
// 5. swap():交换观察目标
weak_ptr<Date> wp2;
wp2.swap(wp); // wp空,wp2关联原资源
cout << "wp2是否过期:" << wp2.expired() << endl; // 0(false)
return 0;
}
weak_ptr 手动实现
#include <iostream>
#include <mutex>
#include <utility> // std::move
using namespace std;
// 提前声明SharedPtr,便于WeakPtr访问其私有成员
template<class T>
class SharedPtr;
template<class T>
class WeakPtr {
public:
// 1. 默认构造:空WeakPtr
WeakPtr() : _ptr(nullptr), _refCount(nullptr), _mtx(nullptr) {}
// 2. 从SharedPtr构造:关联资源,不增加计数
WeakPtr(const SharedPtr<T>& sp)
: _ptr(sp.get()), _refCount(sp._refCount), _mtx(sp._mtx) {
cout << "WeakPtr构造:观察资源 " << _ptr << endl;
}
// 3. 拷贝构造:关联同一资源,不增加计数
WeakPtr(const WeakPtr& wp)
: _ptr(wp._ptr), _refCount(wp._refCount), _mtx(wp._mtx) {
cout << "WeakPtr拷贝构造:观察资源 " << _ptr << endl;
}
// 4. 移动构造:转移观察权,原指针置空
WeakPtr(WeakPtr&& wp) noexcept
: _ptr(wp._ptr), _refCount(wp._refCount), _mtx(wp._mtx) {
wp._ptr = nullptr;
wp._refCount = nullptr;
wp._mtx = nullptr;
cout << "WeakPtr移动构造:转移观察权" << endl;
}
// 5. 赋值重载:关联SharedPtr/WeakPtr
WeakPtr& operator=(const SharedPtr<T>& sp) {
_ptr = sp.get();
_refCount = sp._refCount;
_mtx = sp._mtx;
cout << "WeakPtr赋值:观察资源 " << _ptr << endl;
return *this;
}
WeakPtr& operator=(const WeakPtr& wp) {
if (this != &wp) {
_ptr = wp._ptr;
_refCount = wp._refCount;
_mtx = wp._mtx;
}
return *this;
}
// 6. 析构:仅清空指针,不修改计数(无资源需释放)
~WeakPtr() {
cout << "WeakPtr析构:停止观察 " << _ptr << endl;
_ptr = nullptr;
_refCount = nullptr;
_mtx = nullptr;
}
// 7. 核心函数:lock()转为SharedPtr
SharedPtr<T> lock() const {
if (!expired()) {
// 构造SharedPtr,计数+1(安全访问资源)
return SharedPtr<T>(_ptr, _refCount, _mtx);
}
return SharedPtr<T>(); // 资源过期,返回空SharedPtr
}
// 8. 核心函数:expired()判断资源是否过期
bool expired() const {
if (!_refCount) return true;
// 加锁保证计数读取线程安全
_mtx->lock();
bool isExpired = (*_refCount == 0);
_mtx->unlock();
return isExpired;
}
// 9. 核心函数:reset()清空观察
void reset() {
_ptr = nullptr;
_refCount = nullptr;
_mtx = nullptr;
}
// 10. 核心函数:use_count()获取计数
int use_count() const {
if (!_refCount) return 0;
_mtx->lock();
int count = *_refCount;
_mtx->unlock();
return count;
}
private:
T* _ptr; // 观察的资源指针
int* _refCount; // 关联SharedPtr的引用计数(仅读取,不修改)
mutex* _mtx; // 关联SharedPtr的互斥锁(保证计数读取安全)
};
// 补充SharedPtr的构造函数(供WeakPtr::lock()调用)
template<class T>
class SharedPtr {
public:
// 基础构造(之前的实现)
explicit SharedPtr(T* ptr = nullptr)
: _ptr(ptr), _refCount(ptr ? new int(1) : nullptr), _mtx(ptr ? new mutex : nullptr) {
cout << "SharedPtr构造:接管资源 " << _ptr << ",计数:" << use_count() << endl;
}
// 供WeakPtr::lock()调用的构造函数(共享计数,计数+1)
SharedPtr(T* ptr, int* refCount, mutex* mtx)
: _ptr(ptr), _refCount(refCount), _mtx(mtx) {
if (_refCount) {
_mtx->lock();
++(*_refCount);
_mtx->unlock();
}
cout << "SharedPtr(lock):共享资源 " << _ptr << ",计数:" << use_count() << endl;
}
// 拷贝构造、赋值重载、析构、Release()等(同之前的实现)
SharedPtr(const SharedPtr& sp)
: _ptr(sp._ptr), _refCount(sp._refCount), _mtx(sp._mtx) {
if (_refCount) {
_mtx->lock();
++(*_refCount);
_mtx->unlock();
}
cout << "SharedPtr拷贝构造:计数=" << use_count() << endl;
}
SharedPtr& operator=(const SharedPtr& sp) {
if (this != &sp) {
Release();
_ptr = sp._ptr;
_refCount = sp._refCount;
_mtx = sp._mtx;
if (_refCount) {
_mtx->lock();
++(*_refCount);
_mtx->unlock();
}
cout << "SharedPtr赋值:计数=" << use_count() << endl;
}
return *this;
}
~SharedPtr() {
Release();
}
T& operator*() const {
if (!_ptr) throw runtime_error("空指针解引用");
return *_ptr;
}
T* operator->() const {
if (!_ptr) throw runtime_error("空指针访问成员");
return _ptr;
}
int use_count() const {
return _refCount ? *_refCount : 0;
}
T* get() const { return _ptr; }
void reset(T* ptr = nullptr) {
Release();
_ptr = ptr;
_refCount = ptr ? new int(1) : nullptr;
_mtx = ptr ? new mutex : nullptr;
cout << "SharedPtr::reset():接管新资源 " << _ptr << ",计数=" << use_count() << endl;
}
private:
void Release() {
if (_refCount) {
_mtx->lock();
bool needDelete = false;
if (--(*_refCount) == 0) {
delete _ptr;
delete _refCount;
needDelete = true;
cout << "SharedPtr::Release():释放资源 " << _ptr << endl;
}
_mtx->unlock();
if (needDelete) delete _mtx;
_ptr = nullptr;
_refCount = nullptr;
_mtx = nullptr;
}
}
// 友元:允许WeakPtr访问私有成员
friend class WeakPtr<T>;
private:
T* _ptr;
int* _refCount;
mutex* _mtx;
};
// 测试代码
int main() {
// 1. 关联SharedPtr
SharedPtr<int> sp1(new int(10));
WeakPtr<int> wp1(sp1);
cout << "wp1计数:" << wp1.use_count() << endl; // 1
// 2. lock()安全访问
SharedPtr<int> sp2 = wp1.lock();
cout << "*sp2 = " << *sp2 << endl; // 10
cout << "sp1计数:" << sp1.use_count() << endl; // 2
// 3. 资源释放后expired()
sp1.reset();
sp2.reset();
cout << "wp1是否过期:" << wp1.expired() << endl; // 1(true)
return 0;
}
运行结果
SharedPtr构造:接管资源 0x55f8d8e2c2a0,计数:1
WeakPtr构造:观察资源 0x55f8d8e2c2a0
wp1计数:1
SharedPtr(lock):共享资源 0x55f8d8e2c2a0,计数:2
*sp2 = 10
sp1计数:2
SharedPtr::Release():释放资源 0x55f8d8e2c2a0
SharedPtr::reset():接管新资源 0,计数=0
SharedPtr::Release():释放资源 0
SharedPtr::reset():接管新资源 0,计数=0
wp1是否过期:1
WeakPtr析构:停止观察 0x55f8d8e2c2a0
weak_ptr 优缺点
| 优点 | 场景说明 |
|---|---|
| 解决循环引用 | 彻底解决 shared_ptr 互相引用导致的资源泄漏(如链表、树、观察者模式); |
| 不影响资源释放 | 不增加引用计数,资源释放仅由 shared_ptr 决定,符合「最小权限原则」; |
| 安全观察资源 | expired() 提前判断资源状态,lock() 避免访问悬垂指针; |
| 轻量级 | 无额外性能开销(仅读取计数),比 shared_ptr 更轻量; |
| 线程安全观察 | 计数读取是原子操作,多线程观察资源状态无需手动加锁; |
| 兼容 shared_ptr | 可无缝关联 shared_ptr,lock() 后可复用 shared_ptr 的访问逻辑; |
| 缺点 | 解决方案 |
|---|---|
| 不能直接访问资源 | 必须通过 lock() 转为 shared_ptr,访问前判空(if (sp)); |
| 依赖 shared_ptr | 无法独立使用,需配合 shared_ptr,不能直接管理堆资源; |
| lock () 可能返回空 | 访问前必须判空,避免空指针解引用; |
| 无法单独管理资源 | 仅作为辅助工具,不能替代 shared_ptr/unique_ptr 管理资源; |
| 计数读取非实时 | use_count() 返回的计数可能瞬间变化(仅用于调试,不建议业务逻辑依赖); |
更多推荐

所有评论(0)