智能指针的增强

智能指针的增强
苏丙榅1. std::shared_ptr<T[]>
在 C++ 的世界里,手动管理内存(new 和 delete)是噩梦的开始。忘了 delete 会导致内存泄漏(程序越跑越慢),重复 delete 会导致程序崩溃。
std::shared_ptr 是 C++11 引入的救世主,到了 C++17,它变得更加成熟。可以把 shared_ptr 想象为一个拿着绳子(指针)拽着气球(堆内存对象)的人。
- 引用计数:气球上挂了一个标签,写着 “几根绳子拴着我”。
- 赋值/拷贝:相当于多接了一根绳子,计数 +1。
- 重置/销毁:相当于解开一根绳子,计数 -1。
- 释放:当最后一根绳子解开,气球飞走(自动执行
delete,内存被释放)。
在创建std::shared_ptr的时候,永远优先使用 std::make_shared,不要直接用 new。
1 |
|
在 C++11 和 C++14 中,std::shared_ptr 对数组的支持非常不友善。如果你写过这样的代码:
1 | // ❌ C++11/14 中的错误示范 |
这是一个严重的错误!
p只是一个普通的shared_ptr<int>。- 当
p析构时,它会调用delete,而不是delete[]。 - 结果:分配了
10个整数的数组,却只释放了第1个,剩下的9个造成了内存泄漏。
如果非要在 C++11/14中使用 shared_ptr 管理数组,必须手动指定删除器,代码非常丑陋:
1 | // ✅ C++11 写法(很繁琐,容易写错) |
C++17 允许在模板参数中添加 [] 来表明这是一个数组指针,shared_ptr<T[]> 现在被编译器识别为了一种专门管理数组的智能指针。对于 std::shared_ptr<T>,我们通常使用 *ptr 或 ptr->,但对于 std::shared_ptr<T[]>:
- 析构时自动调用
delete[]。 - 提供了数组下标运算符
operator[],可以直接像数组一样访问。 operator*和operator->被禁用(编译报错)。
1 |
|
注意事项:C++17 中,
std::make_shared依然不支持创建数组!
以下代码在 C++17 标准库中是无法编译的,这个功能直到 C++20 才被加入标准库:
1 | // C++17 编译错误!C++20 编译通过 |
所以在 C++17 中,要老老实实用 new创建对象,类型带上 [],告诉编译器这是数组:
1 | std::shared_ptr<int[]> arr(new int[5]); |
2. std::weak_ptr<T[]>
想象 shared_ptr 是 持有者,手里拿着绳子的气球。而 weak_ptr 只是一个 旁观者。它手里有一个望远镜,透过望远镜看着气球。
- 旁观者不持有绳子:所以
weak_ptr的创建和销毁,不会影响气球的引用计数。 - 气球随时可能飞走:如果所有持有者都松手了(
shared_ptr计数归零),气球(对象)就会爆炸销毁。此时,旁观者透过望远镜看去,只能看到一片虚空。
之所以叫 weak(弱),是因为它对对象的生命周期没有控制权。它不能直接操作对象(不能直接用 -> 或 *),因为它不知道对象是否还活着。它必须先申请变成持有者才能操作。
正如 shared_ptr 有数组版本,weak_ptr 也对应支持了 T[]。它用于解决 shared_ptr 数组的循环引用问题,或者用于观察数组而不持有所有权。
使用 lock() 方法可以将 weak_ptr<T[]> 提升为 shared_ptr<T[]>,从而获得临时使用权:
lock()的作用:尝试获取该对象的一个shared_ptr。- 如果对象活着:成功返回一个指向对象的
shared_ptr,引用计数临时 +1。 - 如果对象死了:返回一个空的
shared_ptr。
1 |
|
C++17 明确规定了 wp.lock() 是一个原子操作。它相当于:检查对象是否存在 并且 如果存在则增加引用计数,这整个动作是不可分割的。也就是说多线程环境下我们可以放心使用该函数。
正确的多线程写法:
1 | // 错误写法 |
expired():仅适用于我不想使用对象,我只是想看看它还在不在的场景。lock():适用于我想使用对象,如果在我就用,不在就算了的场景。
只要我们想访问对象,必须使用 lock(),并且判断 lock()的返回值,千万不要用 expired() 预判。

















