C++ 智能指针 unique_ptr 和 shared_ptr
FreeGuideOnline
最新
2026-07-08
cpp #include #include
struct Widget { Widget(int id) : id_(id) { std::cout << "Widget " << id_ << " created\n"; } ~Widget() { std::cout << "Widget " << id_ << " destroyed\n"; } int id_; };
int main() { // 使用 make_unique(推荐) auto p1 = std::make_unique(10);
// 直接从原始指针构造(需要显式 delete,不推荐,易出错)
std::unique_ptr<Widget> p2(new Widget(20));
// p1 和 p2 在离开作用域时自动销毁
}
#### 移动语义,不能复制
`unique_ptr` **禁止拷贝**,只能移动。移动后源指针变为 `nullptr`,所有权被转移:
```cpp
auto p1 = std::make_unique<int>(5);
// auto p2 = p1; // 编译错误,删除拷贝构造
auto p2 = std::move(p1); // 合法,p1 变为 nullptr,p2 拥有资源
if (p1) {
std::cout << *p1; // 不会执行,因为 p1 为空
}
std::cout << *p2; // 输出 5
访问原始指针
get()返回原始指针,但不会释放所有权。- 像普通指针一样使用
*和->解引用。 - 检查是否为空:
if (p)或p != nullptr。
auto uptr = std::make_unique<std::string>("hello");
if (uptr) {
std::cout << uptr->size(); // 使用 ->
std::cout << (*uptr)[0]; // 使用 *
}
// 不要对 get() 返回的指针执行 delete!
释放与重置
reset()销毁当前对象(若持有),并使unique_ptr变为空。reset(原始指针)接管新指针,旧对象被销毁。release()放弃所有权并返回原始指针,但不销毁对象,需自行管理。
auto ptr = std::make_unique<int>(42);
ptr.reset(); // 释放对象,ptr 为空
ptr.reset(new int(7)); // 接管新的 int(7)
int* raw = ptr.release(); // 放弃所有权,raw 指向 int(7),ptr 为空
delete raw; // 必须手动释放!
自定义删除器
可指定额外的清理逻辑,例如管理 FILE* 或自定义资源:
// 使用 lambda 作为删除器关闭文件
auto file_deleter = [](FILE* f) { if (f) fclose(f); };
std::unique_ptr<FILE, decltype(file_deleter)>
filePtr(fopen("test.txt", "r"), file_deleter);
数组形式
std::unique_ptr 对数组有特化版本 std::unique_ptr<T[]>,会自动调用 delete[]:
auto arr = std::make_unique<int[]>(5); // 分配 int[5]
arr[0] = 1; // 支持下标运算
make_unique 自动推导数组大小,需显式指定长度。
适用场景
- 独占某个资源,如文件句柄、数据库连接。
- 工厂函数返回对象,所有权明确转移给调用者。
- 在容器中管理多态对象(配合移动语义)。
shared_ptr:共享所有权
什么是 shared_ptr
std::shared_ptr 使用引用计数实现共享所有权。多个 shared_ptr 可以指向同一个对象,当最后一个 shared_ptr 被销毁或重置时,对象才会被释放。
创建与引用计数
推荐使用 std::make_shared<T>,它一次性分配对象和控制块,效率更高且异常安全:
#include <memory>
auto sp1 = std::make_shared<int>(10);
auto sp2 = sp1; // sp1 和 sp2 指向同一个 int,引用计数变为2
sp2.reset(); // sp2 不再指向对象,引用计数减为1
// 当 sp1 销毁时,引用计数变 0,释放 int
从原始指针构造也可以,但可能导致问题(见后文)。
内部机制:控制块
每个 shared_ptr 管理一个控制块,记录:
- 引用计数(强引用计数)
- 弱引用计数(给
weak_ptr使用) - 删除器、分配器等信息
使用 make_shared 时,对象内存与控制块在同一内存块中分配,减少动态分配次数。
自定义删除器
与 unique_ptr 类似,但删除器存储在控制块中,不影响 shared_ptr 类型大小:
auto deleter = [](int* p) { delete p; };
std::shared_ptr<int> sp(new int, deleter);
循环引用与 weak_ptr
shared_ptr 的循环引用是著名陷阱:两个对象互相持有对方的 shared_ptr,引用计数永远不为0,导致内存泄漏。
struct B;
struct A {
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed\n"; }
};
struct B {
std::shared_ptr<A> a_ptr;
~B() { std::cout << "B destroyed\n"; }
};
{
auto a = std::make_shared<A>();
auto b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a; // 循环引用,a 和 b 永远不会释放
}