在 C++11 之后,智能指针成为管理动态内存和资源的核心工具。它们帮助开发者避免内存泄漏、悬空指针以及其他与手动 new/delete 相关的错误。本文将从 std::unique_ptr、std::shared_ptr、std::weak_ptr 的使用场景、常见坑以及提升代码可读性与性能的技巧展开讨论。
1. std::unique_ptr——单一所有权的精髓
1.1 基础使用
std::unique_ptr <MyClass> ptr(new MyClass());
或更推荐的 make_unique:
auto ptr = std::make_unique <MyClass>();
unique_ptr 只能被移动,不能复制,确保在同一时间只有一个指针拥有资源。
1.2 自定义删除器
struct FileCloser {
void operator()(FILE* fp) const {
if (fp) fclose(fp);
}
};
auto file = std::unique_ptr<FILE, FileCloser>(fopen("log.txt", "w"), FileCloser());
自定义删除器可用于管理非 new 分配的资源。
1.3 与容器结合
std::vector<std::unique_ptr<Item>> items;
items.emplace_back(std::make_unique <Item>());
容器内部会自动调用删除器,避免忘记 delete。
2. std::shared_ptr——多重所有权与引用计数
2.1 基础使用
auto sp1 = std::make_shared <Widget>();
auto sp2 = sp1; // 引用计数 +1
引用计数采用原子操作,适合多线程环境。
2.2 循环引用警惕
struct Node {
std::shared_ptr <Node> next;
std::shared_ptr <Node> prev;
};
上述两者互相持有 shared_ptr,导致计数永不归零,内存泄漏。解决方案是将其中一个改为 weak_ptr。
2.3 与 std::weak_ptr 结合
std::weak_ptr <Widget> weak = sp1;
if (auto locked = weak.lock()) {
// 成功获取 shared_ptr,使用对象
}
weak_ptr 不参与引用计数,避免循环引用。
3. std::weak_ptr——观察者模式的实现
weak_ptr 主要用于观察者模式、缓存等场景,保持对对象的“非拥有”访问。
class Subject {
public:
void registerObserver(std::shared_ptr <Observer> obs) {
observers_.push_back(obs);
}
void notify() {
for (auto it = observers_.begin(); it != observers_.end(); ) {
if (auto obs = it->lock()) {
obs->update();
++it;
} else {
it = observers_.erase(it); // 已被销毁的观察者移除
}
}
}
private:
std::vector<std::weak_ptr<Observer>> observers_;
};
4. 组合使用:RAII 与智能指针
RAII(Resource Acquisition Is Initialization)与智能指针天然契合。将资源包装在 RAII 对象中,可让异常安全与自动释放同时实现。
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: file_(fopen(path.c_str(), "r")) {
if (!file_) throw std::runtime_error("open failed");
}
~FileHandle() { if (file_) fclose(file_); }
FILE* get() const { return file_; }
private:
FILE* file_;
};
void readFile() {
FileHandle fh("data.txt");
// 读取逻辑
}
5. 性能提示
- 避免不必要的复制:
unique_ptr只允许移动,使用std::move。 - 自定义删除器与内存池:自定义删除器可集成内存池,提升分配速度。
- 使用
make_unique/make_shared:一次性分配对象与计数器,减少内存碎片。 - 慎用
shared_ptr:引用计数开销不容忽视,只有在真正需要多重所有权时才使用。
6. 常见错误与调试技巧
- 悬空
weak_ptr:尝试lock()可能返回空,需始终检查。 - 忘记
std::move:unique_ptr直接赋值会报错,必须显式移动。 - 循环引用:使用
shared_ptr与weak_ptr组合,避免内存泄漏。 - 异常安全:使用智能指针可以显著降低资源泄漏风险,保持代码简洁。
7. 小结
unique_ptr适合独占所有权,适用于大多数情况。shared_ptr用于多所有权,需防止循环引用。weak_ptr用于观察者或缓存,避免所有权持有。- 与 RAII 结合,实现异常安全与资源管理。
通过合理选择与组合上述智能指针,C++ 开发者可以编写更安全、更易维护、性能更高的代码。祝你在编码旅程中愉快地驾驭智能指针的力量!