目录
内存管理库 - cppreference.cn - C++参考手册
👆包含 3 种智能指针的用法,场景,实现
- C++ 开发中,资源管理是一个核心挑战
- 手动管理内存,文件句柄等资源,容易导致(内存泄漏 / 悬垂指针 / 未定义行为)
- 为了解决这些问题,C++ 标准库引入了智能指针 Smart Pointers
- 智能指针三件套:std::unique_ptr, std::shared_ptr, std::weak_ptr
对比
| 特性 | unique_ptr | shared_ptr | weak_ptr |
|---|---|---|---|
| 所有权 | 独占 | 共享 | 无所有权(观察者) |
| 资源管理方式 | 移动语义转移所有权 | 引用计数管理生命周期 | 依赖 shared_ptr |
| 开销 | 裸指针开销(低) | 控制块+原子计数(中) | 共享控制块(中) |
| 场景 | 独占资源管理(文件句柄) | 资源共享(多线程,复杂对象) | 打破循环引用 |
| 线程安全 | 无(需外部同步) | 引用计数线程安全(数据需保护) | 依赖 shared_ptr |
| 实现 | 封装裸指针 | 控制块存储计数,计数为 0 时释放 | 共享控制块,lock() 获取有效指针 |
| 创建方式 | make_unique | make_shared | 从 shared_ptr 构造 |
- unique_ptr 适合独占资源的高效管理
- shared_ptr 用于共享资源的复杂场景
- weak_ptr 辅助 shared_ptr
一,引入背景
C++11 引入的智能指针,是基于 RAII(资源获取即初始化)原则,将资源生命周期与对象生命周期绑定
智能指针在构造时获取资源,在析构时自动释放,不用手动干预
C++ 提供了三种智能指针
- std::unique_ptr:独占资源,单一所有权
- std::shared_ptr:共享资源,多方引用
- std::weak_ptr:观察资源,辅助管理
RAII 的本质
RAII 主张资源要在对象创建时获取,在对象销毁时释放
这种机制确保,即使发生异常或提前返回,资源也能被正确清理
智能指针通过 封装裸指针 和 析构 的逻辑,实现 RAII 自动化
二,std::unique_ptr
独占资源的首选工具
2.1 核心
std::unique_ptr 是 C++11 引入的智能指针,采用独占所有权模型
一个资源只能被一个 unique_ptr 拥有,无法复制,只能通过移动语义转移所有权
这种设计避免了资源被多方同时管理的风险,确保了清晰的所有权归属
- 独占模式:资源归单一指针所有,避免冲突
- 移动支持:通过 std::move 转移所有权,转移后原指针失效
- 高效实现:无额外内存或计算开销,接近裸指针性能
2.2 场景
std::unique_ptr 是智能指针的默认选择,适用以下场景:
- 独占模式:管理文件句柄,数据库连接,确保单一控制
- 临时分配:管理动态对象,避免手动释放
- 所有权转移:工厂函数中返回资源,明确调用者接管责任
2.3 实现
#include <memory>
#include <iostream>
class Device {
public:
Device() { std::cout << "Device initialized\n"; }
~Device() { std::cout << "Device shutdown\n"; }
};
int main() {
// 使用 std::make_unique 创建
auto devPtr1 = std::make_unique<Device>();
// 移动所有权到 devPtr2
auto devPtr2 = std::move(devPtr1);
if (!devPtr1) {
std::cout << "devPtr1 is empty after move\n";
}
// devPtr2 超出作用域,资源自动释放
return 0;
}
Device initialized
devPtr1 is empty after move
Device shutdown
std::make_unique 创建了一个 unique_ptr 管理 Device 对象
所有权从 devPtr1 移动到 devPtr2 后,devPtr1 变为 nullptr
当 devPtr2 超出作用域时,Device 对象被自动销毁
2.4 性能与安全
- 性能:std::_unique_ptr 运行时开销极低,不需要维护引用计数或额外的数据结构,仅封装了裸指针和析构
- 安全:std::unique_ptr 不提供内置线程安全机制,多线程访问需要外部的同步保护
2.5 建议
- 优先选择:管理动态资源首选 unique_ptr,简单高效
- 工厂函数:使用 std::make_unique 创建,避免直接 new,提高异常安全性
- 接口设计:在 API 种使用 unique_ptr 明确所有权转移语义
三,std::shared_ptr
共享资源的协作者
3.1 核心
std::shared_ptr 采用共享所有权模型
通过引用计数管理资源生命周期,允许多个 shared_ptr 同时引用同一资源
引用计数记录当前有多少指针引用资源,只有计数为 0,资源才会被释放
- 共享模式:支持多方引用,资源生命周期由引用计数控制
- 复制支持:可以复制 shared_ptr,每次复制增加计数
- 动态管理:资源在最后一个引用消失时自动释放
3.2 场景
- 多线程共享:资源在多个线程间传递,引用计数确保安全释放
- 复杂关系:对象间存在多重引用,无法明确单一所有者
- 动态模块:插件或动态加载系统需要共享核心资源
3.3 实现
#include <memory>
#include <iostream>
class SharedAsset {
public:
SharedAsset() { std::cout << "SharedAsset created\n"; }
~SharedAsset() { std::cout << "SharedAsset destroyed\n"; }
};
int main() {
// 使用 std::make_shared 创建
auto asset1 = std::make_shared<SharedAsset>();
std::cout << "Count after asset1: " << asset1.use_count() << "\n";
// 复制到 asset2,增加计数
auto asset2 = asset1;
std::cout << "Count after asset2: " << asset1.use_count() << "\n";
// 释放 asset1,计数减少
asset1.reset();
std::cout << "Count after asset1 reset: " << asset2.use_count() << "\n";
// asset2 超出作用域,计数为 0,资源释放
return 0;
}
SharedAsset created
Count after asset1: 1
Count after asset2: 2
Count after asset1 reset: 1
SharedAsset destroyed
3.4 性能和安全
- 性能:std::shared_ptr 的开销高于 unique_ptr,因为要维护控制块(16 字节)和原子计数操作
- 线程安全:引用计数操作是线程安全的,但资源本身需额外保护
3.5 建议
- 谨慎使用:仅在共享资源不可避免时,使用 shared_ptr,否则优先 unique_ptr
- 工厂函数:使用 std::make_shared 创建,提高内存分配效率
- 避免循环:警惕循环引用,必要时引入 weak_ptr 解决
四,std::weak_ptr
资源观察的辅助利器
4.1 特性
std::weak_ptr 采用观察者模型,不拥有资源所有权,只观察 shared_ptr 管理的资源
它不影响引用计数,所以不会阻止资源释放
- 无所有权:不参与资源管理,仅提供访问入口
- 动态检查:通过 lock() 获取关联 shared_ptr,检查资源是否有效
- 辅助功能:主要用于解决 shared_ptr 循环引用的问题
4.2 场景
- 循环引用解决:对象互相引用时,使用 weak_ptr 避免资源泄漏
- 资源缓存:观察资源状态,资源释放后自动失效
- 非侵入访问:需要在不影响资源生命周期的情况下,访问资源
4.3 实现
#include <memory>
#include <iostream>
class CachedItem {
public:
CachedItem() { std::cout << "CachedItem created\n"; }
~CachedItem() { std::cout << "CachedItem destroyed\n"; }
};
int main() {
// 创建 shared_ptr
auto item = std::make_shared<CachedItem>();
std::weak_ptr<CachedItem> observer = item; // 观察 item
// use_count() 返回引用计数的数值:多少个 shared_ptr 对象引用同一资源
std::cout << "Reference count: " << item.use_count() << "\n";
// 尝试获取一个 shared_ptr 对象,检查资源是否有效
// 通过 lock() 访问资源
if (auto locked = observer.lock()) {
std::cout << "Item accessible via observer\n";
}
// 释放资源并置空:对于 share 引用计数减 1,对于 unique 直接释放
item.reset();
if (auto locked = observer.lock()) {
std::cout << "This won't print\n";
} else {
std::cout << "Item released, observer invalid\n";
}
return 0;
}
CachedItem created
Reference count: 1
Item accessible via observer
CachedItem destroyed
Item released, observer invalid
上述例子中,std::weak_ptr 观察 shared_ptr 管理的资源,不影响计数
当 shared_ptr 释放资源后,weak_ptr 通过 lock() 检查返现资源已不可用
4.4 性能
- 性能:std::weak_ptr 与关联的 shared_ptr 共享控制块,开销类似,但不参与计数管理
- 线程安全:依赖 shared_ptr,访问需通过 lock() 检查有效性
4.5 建议
- 配合使用:必须与 shared_ptr 搭配,无法独立管理资源
- 有效性检查:调用 lock() 后立即检查返回的 shared_ptr 是否有效
- 特定场景:仅用于解决 循环引用 或观察资源
五,最佳实践
- 工厂函数:使用 std::make_unique 和 std::make_shared 创建,避免直接 new
- 避免裸指针:尽量不直接操作裸指针,把资源管理交给智能指针
- 语义清晰:API 设计中,通过智能指针类型表达所有权归属
- 避免混淆:不与裸指针混用,防止所有权混乱
- 数据保护:多线程环境,智能指针不保证资源数据安全,需要额外同步
- 共享范围:使用 shared_ptr 时,尽量缩小共享范围,减少开销
- 循环引用:shared_ptr 使用时,涉及循环引用,要用 weak_ptr
- 过度依赖:栈上对象不用智能指针,避免滥用
- 全局变量:避免全局的智能指针,构造细狗顺序可能引发问题
六,智能指针--实现
- unique_ptr
- 内部指针:封装裸指针,管理资源
- 移动操作:移动语义转移所有权
- 析构释放:释放资源
- shared_ptr
- 控制块:存储引用计数和删除器
- 原子计数:确保线程安全更新计数
- 资源释放:计数为 0 时释放资源
- weak_ptr
- 共享控制:与 shared_ptr 共享控制块
- 不增计数:不影响资源生命周期
- 动态检查:通过 lock() 检查资源状态
777

被折叠的 条评论
为什么被折叠?



