在现代 C++ 开发中,手动管理内存已经逐渐被智能指针取代。智能指针能够自动处理资源生命周期,显著减少内存泄漏和悬空指针的风险。本篇文章将从概念、使用场景、常见陷阱以及最佳实践四个方面,对 std::shared_ptr 与 std::unique_ptr 做一次系统梳理。
一、智能指针概览
| 指针类型 | 所有权模型 | 典型使用场景 |
|---|---|---|
| `std::unique_ptr | ||
| ` | 独占所有权 | 临时对象、单一所有者、资源包装器 |
| `std::shared_ptr | ||
| ` | 共享所有权 | 对象需要多处共享、跨模块共享、递归结构 |
注意:
std::unique_ptr采用“移动语义”,不支持拷贝;std::shared_ptr采用引用计数,支持拷贝和移动,但需注意循环引用。
二、std::unique_ptr 的使用要点
1. 创建与转移
std::unique_ptr <Foo> p1(new Foo); // 直接创建
auto p2 = std::make_unique <Foo>(); // 推荐方式,避免手动 new
std::unique_ptr <Foo> p3 = std::move(p1); // 转移所有权
- 不要使用
new直接构造unique_ptr;make_unique更安全、可读性更好。
2. 与容器配合
std::vector<std::unique_ptr<Foo>> vec;
vec.emplace_back(std::make_unique <Foo>());
- 禁止将
unique_ptr复制到容器中,必须使用emplace_back或push_back(std::move(...))。
3. 自定义删除器
std::unique_ptr<FILE, decltype(&fclose)> filePtr(fopen("file.txt", "r"), &fclose);
- 当资源不是使用
new/delete管理时,可以提供自定义删除器。
4. 循环引用预防
unique_ptr 本身不会导致循环引用,但与 std::shared_ptr 混用时要特别注意。例如,父子节点关系中,子节点用 unique_ptr 指向父节点,父节点用 shared_ptr 指向子节点,避免双向引用导致内存泄漏。
三、std::shared_ptr 的使用要点
1. 复制与引用计数
std::shared_ptr <Foo> sp1 = std::make_shared<Foo>();
std::shared_ptr <Foo> sp2 = sp1; // 计数 +1
- 复制会共享所有权,计数会自动增加,销毁时计数递减。
2. 循环引用问题
struct Node {
std::shared_ptr <Node> next;
std::weak_ptr <Node> prev; // 使用 weak_ptr 打破循环
};
weak_ptr只观察而不计数,可通过lock()获得shared_ptr。
3. 线程安全
- 标准库实现的
std::shared_ptr的引用计数是原子操作,适合多线程场景,但对对象内部状态的同步需自行处理。
4. 性能注意
shared_ptr需要两块内存:对象本身 + 控制块(计数)。如果频繁创建小对象,考虑使用unique_ptr或手工内存池。
四、智能指针的最佳实践
-
首选
std::unique_ptr
只有在确实需要共享所有权时才使用shared_ptr。 -
避免裸指针与智能指针混用
如果返回裸指针,明确其所有权归属,最好通过std::shared_ptr或std::unique_ptr明确返回类型。 -
在异常安全代码中使用
智能指针天然符合 RAII,能在异常抛出时自动析构。 -
结合
std::optional使用
对可能为空的对象,使用std::optional<std::unique_ptr<T>>进行包装,避免返回裸指针。 -
使用
std::make_unique/std::make_shared
统一资源创建方式,提升性能与可读性。
五、实战案例:资源包装器
class FileHandle {
std::unique_ptr<FILE, decltype(&fclose)> file_;
public:
explicit FileHandle(const std::string& path)
: file_(fopen(path.c_str(), "r"), &fclose) {
if (!file_) throw std::runtime_error("Open file failed");
}
// 提供读取接口
std::string read() {
std::string line, result;
while (fgets(line, 1024, file_.get())) {
result += line;
}
return result;
}
};
FileHandle内部使用unique_ptr自动管理FILE*,无须手动关闭文件。
六、总结
std::unique_ptr:独占所有权,轻量级,适合单一拥有者场景。std::shared_ptr:共享所有权,需关注循环引用。- 通过
make_unique/make_shared创建,使用weak_ptr预防循环,结合异常安全的 RAII,能够让 C++ 程序更健壮、更易维护。
智能指针是 C++ 现代化内存管理的核心工具,正确使用它们可以极大减少内存错误,提升代码质量。祝编码愉快!