在现代 C++ 开发中,智能指针已成为管理资源的核心工具。它们通过 RAII(资源获取即初始化)机制,保证了对象生命周期的一致性,显著降低了内存泄漏和悬挂指针的风险。本文将聚焦于两大主流智能指针:std::shared_ptr 与 std::unique_ptr,探讨它们在不同场景下的使用细节、优势与潜在陷阱,并给出实际编码示例。
1. std::unique_ptr——独占所有权的简洁实现
std::unique_ptr 表示独占式所有权,即同一时间只能有一个 unique_ptr 拥有某个对象。它提供了以下特性:
- 无引用计数:性能开销极低,适合临时对象或内部资源管理。
- 自动销毁:当
unique_ptr离开作用域,持有的对象被delete释放。 - 转移语义:通过
std::move可以将所有权从一个指针转移到另一个。
#include <memory>
#include <iostream>
struct Widget {
Widget() { std::cout << "Widget constructed\n"; }
~Widget() { std::cout << "Widget destroyed\n"; }
};
int main() {
std::unique_ptr <Widget> p1(new Widget); // 自动析构
std::unique_ptr <Widget> p2 = std::move(p1); // 所有权转移
if (!p1) std::cout << "p1 is empty\n";
}
1.1 自定义删除器
unique_ptr 可以接受自定义删除器,方便与非标准分配器或资源类型配合使用。
struct FileDeleter {
void operator()(FILE* f) const {
if (f) fclose(f);
}
};
using FilePtr = std::unique_ptr<FILE, FileDeleter>;
FilePtr fp(fopen("log.txt", "w"), FileDeleter{});
2. std::shared_ptr——引用计数的共享所有权
std::shared_ptr 允许多个指针实例共享同一对象,并维护内部引用计数。其特点包括:
- 线程安全的引用计数:在多线程环境下,计数自增/自减是原子操作。
- 延迟销毁:对象在最后一个
shared_ptr被销毁时才被释放。 - 弱引用:
std::weak_ptr允许查看对象但不参与计数,避免循环引用。
#include <memory>
#include <iostream>
int main() {
auto sp1 = std::make_shared <int>(42);
std::weak_ptr <int> wp = sp1; // 不计数
if (auto sp2 = wp.lock()) { // 获取共享指针
std::cout << "Value: " << *sp2 << '\n';
}
}
2.1 循环引用的危害
当两个对象通过 shared_ptr 互相引用时,引用计数永远不为零,导致内存泄漏。使用 weak_ptr 可以打破循环。
struct B; // 前向声明
struct A {
std::shared_ptr <B> bPtr;
};
struct B {
std::weak_ptr <A> aPtr; // 使用 weak_ptr
};
3. 与标准容器的配合
大多数标准容器(如 std::vector、std::map)都能存放 unique_ptr 或 shared_ptr,但需注意:
vector<unique_ptr<T>>需要自定义移动构造函数或使用std::move。map<key, shared_ptr<T>>可以轻松实现多路复用资源。
std::vector<std::unique_ptr<Widget>> widgets;
widgets.emplace_back(std::make_unique <Widget>());
4. 性能考虑
| 智能指针 | 内存占用 | 计数管理 | 典型场景 |
|---|---|---|---|
unique_ptr |
1 个指针 | 无 | 栈内对象、临时资源 |
shared_ptr |
1 个指针 + 计数器 | 原子操作 | 需要共享、跨线程共享 |
在性能敏感的代码路径上,尽量使用 unique_ptr。只有在明确需要共享所有权时才引入 shared_ptr,并避免不必要的引用计数操作。
5. 小结
unique_ptr:轻量、独占、不可复制;适用于绝大多数资源管理。shared_ptr:可复制、引用计数、线程安全;适用于真正需要共享所有权的场景。weak_ptr:避免循环引用,提供“观察者”模式。
通过合理选择与组合这些智能指针,你可以写出更安全、可维护、且性能友好的 C++ 代码。