C++智能指针
std::unique_ptr
对于独占资源(exclusive-ownership resource)使用std::unique_ptr,它禁止和其他智能指针共享对象
使用
与std::shared_ptr
只有单个对象形式不同, std::unique_ptr
有两种形式, 对于单个对象(std::unique_ptr<T>
), 对于数组(std::unique_ptr<T[]>
), 对于解引用操作符(operator*
和operator->
)对于单个对象专有。
std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法
C++ 并没有提供 std::make_unique, 可自行实现
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}
类型
对于std::unique_ptr
来说,删除器是类型的一部分。
占用空间
当使用默认删除器(不使用自定义删除器)的时候,std::unique_ptr
的对象大小和原始指针大小相同,都为一个字(word)。当使用自定义删除器的时候,对于无状态(stateless function)的对象,不捕获变量的lambda表达式(下面的del_func), std::unique_ptr
大小增加为两个字(word);当捕获外部变量时,std::unique_ptr的大小取决于,函数对象中存储的状态。
void func_unique_ptr_size() {
cout << "ptr size:" << sizeof(new Weight()) << endl;
unique_ptr<Weight> default_del_unique_ptr(new Weight());
cout << "default_del_unique_ptr size: " << sizeof (default_del_unique_ptr) << endl;
auto del_func = [](Weight* w){delete w;};
unique_ptr<Weight, decltype(del_func)> custom_delete_func_unique_ptr(new Weight(), del_func);
cout << "custom_delete_func_unique_ptr size: " << sizeof(custom_delete_func_unique_ptr) << endl;
int i = 100;
auto del_func_stateful = [&i](Weight* w) {int x = i; delete w;};
unique_ptr<Weight, decltype(del_func_stateful)> custom_stateful_del_func_unique_ptr(new Weight, del_func_stateful);
cout << "custom_stateful_del_func_unique_ptr size: " << sizeof(custom_stateful_del_func_unique_ptr) << endl;
}
输出
ptr size:8
default_del_unique_ptr size: 8
custom_delete_func_unique_ptr size: 8
custom_stateful_del_func_unique_ptr size: 16
std::shared_ptr
对于共享资源(shared-ownership resource) 使用 std::shared_ptr
使用
std::shared_ptr
能够记录有多少个std::shared_ptr
指向同一个对象,当引用计数为0的时,会自动调用delete
删除对象。
可以通过get()
获取原始指针。通过use_count()来查看一个对象的引用计数。当智能指针中有值时,通过reset()
来减少一个引用计数,调用 reset(new xxx())
重新赋值,并且引用计数置为1。
内部实现
std::shared_ptr
的大小是原始指针的两倍,包含一个资源的原始指针,和指向控制块(control block)的指针。当使用原始指针创建std::shared_ptr
时,控制块的内存会动态分配,使用make_shared
可以避免。
当使用自定义删除器时,不会改变shared_ptr
的大小,因为自定义删除器保存在shared_ptr
内指针指向的内存。
控制块创建的时机
- 使用
make_shared
时总会创建一个控制块shared_ptr<Weight> sp0 = std::make_shared<Weight>()
从独占指针
unique_ptr
上构造shared_ptr
时会创建控制快unique_ptr<Weight> uniquePtr(new Weight()); shared_ptr<Weight> sp1 = move(uniquePtr); cout << uniquePtr << endl; // 0x0
在原始指针上构造
shared_ptr
会创建控制块。但如果从已经存在控制块的对象上创建shared_ptr
不会创建新的控制块,可以依赖传递来的智能指针指向控制块。shared_ptr<Monster> spM0(new Monster("m0")); cout << spM0.use_count() << endl; // 1 shared_ptr<Monster> spM1(spM0); // 此处不创建新的控制块, spM1的指向控制快地址和spM0相同 cout << spM0.use_count() << endl; // 2
使用注意点
- 当在一个原始指针上创建多个
shared_ptr
,也就是说由多个具有不同的控制块的shared_ptr
管理同一份资源。多个控制块意味着多个引用计数值,多个引用计数值意味着对象将会被销毁多次(每个引用计数一次)
因此,在创建auto pw = new Widget; //pw是原始指针 std::shared_ptr<Widget> spw1(pw, loggingDel); //为*pw创建控制块 std::shared_ptr<Widget> spw2(pw, loggingDel); //为*pw创建第二个控制块
shared_ptr
时,最好使用make_shared
, 不过这样无法自定义删除器。必须给shared_ptr
构造函数原始指针时,直接传递 new 出来的匿名对象。 - 如果你想创建一个用std::shared_ptr管理的类,这个类能够用this指针安全地创建一个std::shared_ptr,std::enable_shared_from_this就可作为基类的模板类。
class Widget: public std::enable_shared_from_this<Widget> { public: … void process(); … };
- shared_ptr不支持数组对象
std::shared_ptr<T[]>
std::weak_ptr
当std::shard_ptr可能悬空(dangle)时使用std::weak_ptr
weak_ptr
是一个可以不需要手动管理内存,同时不参与资源所有权共享的智能指针。它不是一个可以独立存在的指针,是shared_ptr
的增强。
使用
- 可以在
shared_ptr
上创建weak_ptr
, 在两个指针上使用use_count()
获取到的都是指向资源的shared_ptr
的个数shared_ptr<Weight> sp = make_shared<Weight>(); weak_ptr<Weight> wp0(sp); cout << sp.use_count() << endl; // 1 weak_ptr<Weight> wp1(sp); cout << sp.use_count() << endl; // 1 cout << wp1.use_count() << endl; // 1
- 可以通过一个原子操作检查
weak_ptr
是否已经过期,如果没有过期就访问指向对象。可以通过在weak_ptr
上创建shared_ptr
实现。有两种方式:shared_ptr<Weight> sp1 = wp0.lock(); if (sp1 == nullptr) { cout << "dangle" << endl; } shared_ptr<Weight> sp2(wp0); // 如果wp0过期, 则抛出std::bad_weak_ptr异常
- 打破
std::shared_ptr
环状结构
使用 std::make_shared 和 std::make_unique
优点(建议使用的场景)
- 避免类型重复
auto upw1(std::make_unique<Widget>()); //使用make函数, 只写Weight一次类型 std::unique_ptr<Widget> upw2(new Widget); //不使用make函数, 需要写两次
- 异常安全(exception safe)
对于函数
void processWidget(std::shared_ptr<Widget> spw, int priority);
有一个函数int computePriority();
计算优先级, 下面代码存在潜在的内存泄露。
一个函数的实参必须先被计算,这个函数再被调用,processWidget(std::shared_ptr<Widget>(new Widget), computePriority());
new Widget
必须在std::shared_ptr
的构造函数被调用前执行,因为new出来的结果作为构造函数的实参,但computePriority
可能在这之前,之后,或者之间执行。执行“new Widget” 执行computePriority // 如果在这里抛出异常,会导致 new Weiget的资源无法释放。 运行std::shared_ptr构造函数
- 效率提升
对于
std::shared_ptr<Widget> spw(new Widget);
需要进行两次内存分配。一次使用new
分配Weight
的内存。 一次为shared_ptr
分配控制块内存。 使用make_shared
增加内存分配效率,并且潜在的减少了程序的总内存占用。
不足(不建议使用的场景)
- 自定义删除器
auto widgetDeleter = [](Widget* pw) { … }; std::shared_ptr<Widget> spw(new Widget, widgetDeleter);
- 使用花括号初始化
ref
https://www.cnblogs.com/KillerAery/p/9096558.html https://juejin.cn/post/6844903993055920141#heading-14 https://heleifz.github.io/14696398760857.html https://cntransgroup.github.io/EffectiveModernCppChinese/4.SmartPointers/item19.html https://changkun.de/modern-cpp/zh-cn/05-pointers/
Subscribe to my newsletter
Read articles from qilingzhao directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by