C++智能指针
智能指针
std::shared_ptr
-
核心特性:共享所有权的智能指针,多个
shared_ptr
可以指向同一个对象,内部通过引用计数(reference count)跟踪对象被引用的次数。当最后一个指向该对象的shared_ptr
被销毁时,对象才会被自动删除。 -
适用场景:需要多个所有者共享资源的场景,如容器中存储的对象、多个指针指向同一动态对象等。
-
基本用法
#include <memory> #include <iostream> int main() { // 创建指向int的shared_ptr(推荐使用make_shared,更高效) auto sptr1 = std::make_shared<int>(200); std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出:1 // 拷贝,引用计数增加 std::shared_ptr<int> sptr2 = sptr1; std::cout << "引用计数: " << sptr1.use_count() << std::endl; // 输出:2 // 重置sptr1,引用计数减少 sptr1.reset(); std::cout << "引用计数: " << sptr2.use_count() << std::endl; // 输出:1 return 0; // sptr2销毁时,引用计数变为0,释放内存 }
-
注意:避免循环引用(如两个
shared_ptr
互相指向对方),否则引用计数永远不会归零,导致内存泄漏(需配合weak_ptr
解决)。
std::unique_ptr
-
核心特性:独占所有权的智能指针,同一时间只能有一个
unique_ptr
指向某个对象,不允许拷贝(但允许移动)。当unique_ptr
被销毁(如离开作用域)时,它所指向的对象会自动被删除。 -
适用场景:管理独占所有权的资源,如动态分配的单个对象或数组,是替代
auto_ptr
的现代方案。 -
基本用法
#include <memory> int main() { // 创建指向int的unique_ptr std::unique_ptr<int> uptr1(new int(42)); // 或使用更安全的make_unique(C++14引入) auto uptr2 = std::make_unique<int>(100); // 不允许拷贝 // std::unique_ptr<int> uptr3 = uptr1; // 编译错误 // 允许移动(所有权转移) std::unique_ptr<int> uptr4 = std::move(uptr1); // uptr1变为nullptr return 0; // uptr2、uptr4销毁时,自动释放所指向的内存 }
-
优势:轻量级(内存开销小),效率高,编译期检查所有权,避免意外拷贝。
std::weak_ptr
-
核心特性:弱引用智能指针,不拥有对象的所有权,仅作为
shared_ptr
的辅助工具。它指向shared_ptr
管理的对象,但不增加引用计数,因此不会影响对象的生命周期。 -
适用场景:解决
shared_ptr
的循环引用问题,或需要临时访问某个资源但不希望延长其生命周期的场景。 -
基本用法
#include <memory> #include <iostream> int main() { auto sptr = std::make_shared<int>(300); std::weak_ptr<int> wptr = sptr; // 弱引用,不增加引用计数 std::cout << "sptr引用计数: " << sptr.use_count() << std::endl; // 输出:1 // 通过lock()获取shared_ptr(若对象已释放,返回nullptr) if (auto temp = wptr.lock()) { std::cout << "对象值: " << *temp << std::endl; // 输出:300 } else { std::cout << "对象已释放" << std::endl; } sptr.reset(); // 释放对象,引用计数变为0 if (auto temp = wptr.lock()) { std::cout << "对象值: " << *temp << std::endl; } else { std::cout << "对象已释放" << std::endl; // 输出此句 } return 0; }
-
核心方法:
lock()
用于获取一个指向对象的shared_ptr
(若对象存在),expired()
用于检查所指向的对象是否已被释放。
总结对比
智能指针 | 所有权 | 引用计数 | 主要用途 | 缺点 |
---|---|---|---|---|
unique_ptr |
独占 | 无 | 管理独占资源,替代原始指针 | 不可拷贝,仅可移动 |
shared_ptr |
共享 | 有 | 多所有者共享资源 | 有额外内存开销,可能循环引用 |
weak_ptr |
无(弱引用) | 不影响 | 解决循环引用,临时访问资源 | 不能直接访问对象,需通过lock() |
实际开发中,应根据资源的所有权关系选择合适的智能指针:优先使用unique_ptr
(效率最高),需共享时使用shared_ptr
,配合weak_ptr
解决循环引用问题。
另外相比new,make_xxx
更具有优越性.具体原因无需说明,单从概念即可看出,new的话要构造两次.
使用
智能指针能如普通指针一样使用,因为被重载了.
.get()
获取原始指针
.release
取消托管,需要自己手动释放.
.reset(xxx)
重置指针托管的地址,即释放掉原本托管的内存,用参数智能代替,如xxx
为空则是直接释放.
自定义删除器
RAII(Resource Acquisition Is Initialization)是由c++之父Bjarne Stroustrup提出的,中文翻译为资源获取即初始化.
我觉得这句很正确,其实就和我们强制写0是一样的,增加代码可控性.那么我们可以利用智能指针的特性来实现这个.
但是我们会发现智能指针设置得很自由,有些销毁方式不是单纯的delete
能解决的,所以智能指针是有自定义删除器的功能的,有三种:
- 函数指针
- Lambda:无状态的 Lambda(不捕获任何变量)或空的函数对象作为删除器不会增加
unique_ptr
的大小,这是一种零成本抽象。但如果使用函数指针或者有状态的删除器,unique_ptr
的大小会增加。 - 仿函数
std::unique_ptr<T, DeleterType>
和std::shared_ptr<T>
不同,单从类型可以看出,如果unique要自定义删除器,要实例化的时候写上相应删除器类型.
std::unique
的pointer
通常,当我们写 std::unique_ptr<T, Deleter>
时,unique_ptr
内部会认为它管理的是一个类型为 T*
的指针。它提供的成员函数,比如 get()
,就会返回一个 T*
类型。
例如,对于 std::unique_ptr<int>
,它内部管理的指针类型是 int*
。
但是问题出现了,如果当 T
本身就是句柄或指针类型时怎么办.
构建一个情景,T
是 HANDLE
。在 Windows SDK 中,HANDLE
本身通常就是 void*
的一个别名。
如果我们不进行任何自定义,直接使用 std::unique_ptr<HANDLE, HandleDeleter>
,那么 unique_ptr
会默认它管理的指针类型是 void**
。这个问题不言而喻了.
故而在 std::unique_ptr<T, Deleter>
会检查其删除器类型 Deleter
中是否存在一个名为 pointer
的嵌套类型,来告诉它T应该是什么。当然这里只有仿函数可以实现了.
下例:
struct HandleDeleter {
// 告诉 unique_ptr,你管理的“指针”类型其实是 HANDLE
using pointer = HANDLE;
void operator()(HANDLE handle) const {
if (handle && handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
}
};
// T 是 HANDLE, Deleter 是 HandleDeleter
// 因为 HandleDeleter::pointer 存在,所以 UniqueHandle 内部的指针类型是 HANDLE,而不是 HANDLE*
using UniqueHandle = std::unique_ptr<HANDLE, HandleDeleter>;
这两点让unique_ptr
可以实现RAII的包装.