C++ 智能指针:unique_ptr、shared_ptr 与 weak_ptr 实战解析
在 C++ 中,手动管理动态内存极易引发内存泄漏或重复释放。为解决这一问题,标准库引入了智能指针,通过 RAII(资源获取即初始化)机制,在对象生命周期结束时自动释放资源,从而提升程序的异常安全性与资源管理可靠性。
unique_ptr:独占所有权
std::unique_ptr 表示对资源的独占控制,不允许复制,仅支持移动语义。其设计目标是轻量、高效,适用于资源生命周期明确、无需共享的场景,如封装内部实现(pImpl 模式)或作为容器元素。
#include <memory>
#include <iostream>
class Engine {
public:
void start() const { std::cout << "Engine started.\n"; }
};
// 创建与移动
auto engine1 = std::make_unique<Engine>();
engine1->start();
auto engine2 = std::move(engine1); // 资源转移,engine1 变为空
if (engine1 == nullptr) {
std::cout << "engine1 is null.\n";
}
engine2->start(); // 正常使用
// 手动释放
engine2.reset(); // 立即释放资源
unique_ptr 支持数组类型,使用时需显式声明为 unique_ptr<T[]>,此时默认使用 delete[] 释放:
auto buffer = std::make_unique<int[]>(100);
for (int i = 0; i < 100; ++i) {
buffer[i] = i * 2;
}
// 自动调用 delete[],无需手动干预
shared_ptr:共享所有权与引用计数
std::shared_ptr 通过引用计数实现多个指针共享同一资源。当最后一个 shared_ptr 被销毁或重置时,资源才被释放。适用于多模块共享对象、回调注册等场景。
auto data = std::make_shared<std::string>("Shared data");
auto copy1 = data; // 引用计数:2
auto copy2 = data; // 引用计数:3
std::cout << "Use count: " << data.use_count() << "\n"; // 输出 3
copy1.reset(); // 引用计数:2
std::cout << "Use count after reset: " << data.use_count() << "\n"; // 输出 2
但需注意:若多个 shared_ptr 彼此循环引用,将导致内存泄漏:
class Node {
public:
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a; // 循环引用!引用计数永不归零
weak_ptr:打破循环引用
std::weak_ptr 不增加引用计数,仅作为"观察者"访问 shared_ptr 管理的对象。它用于解决循环引用问题,使用前需通过 lock() 转换为临时 shared_ptr 检查对象是否仍存活。
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 改为 weak_ptr
};
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->prev = a; // 不增加 a 的引用计数
if (auto p = b->prev.lock()) {
std::cout << "Node is still alive.\n";
} else {
std::cout << "Node has been destroyed.\n";
}
若 weak_ptr 指向的对象已被释放,调用 lock() 返回空 shared_ptr,或直接构造 shared_ptr 会抛出 std::bad_weak_ptr 异常。
enable_shared_from_this:安全获取 this 的 shared_ptr
当类成员函数需返回指向自身的 shared_ptr 时,不能直接使用 std::shared_ptr<this>,否则会创建多个独立的引用计数,导致双重释放。
继承 std::enable_shared_from_this<T> 后,可通过 shared_from_this() 安全获取共享指针:
#include <memory>
class Task : public std::enable_shared_from_this<Task> {
public:
void schedule() {
auto self = shared_from_this(); // 安全获取
// 传递给异步任务或回调
std::thread([self]() {
self->execute();
}).detach();
}
void execute() { std::cout << "Task executed.\n"; }
};
int main() {
auto task = std::make_shared<Task>();
task->schedule(); // 安全,不会导致双重释放
}
make_unique 与 make_shared:推荐的构造方式
应优先使用 std::make_unique 和 std::make_shared 创建智能指针,而非直接调用 new。
make_shared在一次内存分配中同时创建对象和控制块,减少内存碎片,提升性能。make_unique更安全,避免因异常导致的资源泄漏。- 两者均支持构造参数转发,语法简洁。
auto ptr1 = std::make_unique<Engine>(); // 推荐
auto ptr2 = std::make_shared<std::vector<int>>(100, 42); // 推荐
// 不推荐:可能泄漏
auto bad = std::shared_ptr<Engine>(new Engine());
shared_ptr 与数组:自定义删除器
shared_ptr 默认使用 delete,对数组不适用。若需管理数组,必须指定 std::default_delete<T[]>:
auto arr = std::shared_ptr<int[]>(
new int[50],
std::default_delete<int[]>()
);
for (int i = 0; i < 50; ++i) {
arr[i] = i * 3;
}
// 正确调用 delete[],避免未定义行为
现代 C++ 推荐使用 std::vector 替代裸数组,仅在与 C 接口交互或性能关键路径中才使用数组形式的智能指针。