C++ 标准库读书杂记7 Smart Pointer
发布日期:2021-05-10 05:00:41 浏览次数:23 分类:技术文章

本文共 11086 字,大约阅读时间需要 36 分钟。

C++没有内存回收机制,每次程序员new出来的对象需要手动delete,流程复杂时可能会漏掉delete,导致内存泄漏。于是C++引入智能指针,可用于动态资源管理,资源即对象的管理策略。

使用 raw pointer 管理动态内存时,经常会遇到这样的问题:

  • 忘记delete内存,造成内存泄露。
  • 出现异常时,不会执行delete,造成内存泄露。

智能指针主要有三种:shared_ptr,unique_ptr和weak_ptr。

 shared_ptr

shared_ptr是最常用的智能指针(项目中我只用过shared_ptr)。shared_ptr采用了引用计数器,多个shared_ptr中的T *ptr指向同一个内存区域(同一个对象),并共同维护同一个引用计数器。shared_ptr定义如下,记录同一个实例被引用的次数,当引用次数大于0时可用,等于0时释放内存。

注意避免循环引用,shared_ptr的一个最大的陷阱是循环引用,循环,循环引用会导致堆内存无法正确释放,导致内存泄漏。循环引用在weak_ptr中介绍。引用传递不加1;

shared_ptr对象每次离开作用域时会自动调用析构函数,而析构函数并不像其他类的析构函数一样,而是在释放内存是先判断引用计数器是否为0。等于0才做delete操作,否则只对引用计数器左减一操作。

接下来看一下构造函数,默认构造函数的引用计数器为0,ptr指向NULL:

SharedPtr() : _ptr((T *)0), _refCount(0){}~SharedPtr(){    if (_ptr && --*_refCount == 0) {        delete _ptr;        delete _refCount;    }}

用普通指针初始化智能指针时,引用计数器初始化为1:

SharedPtr(T *obj) : _ptr(obj), _refCount(new int(1)){}

赋值运算符的重载

当用一个shared_ptr<T> other去给另一个 shared_ptr<T> sp赋值时,发生了两件事情:

一、sp指针指向发生变化,不再指向之前的内存区域,所以赋值前原来的_refCount要自减

二、sp指针指向other.ptr,所以other的引用计数器_refCount要做++操作。

template 
class shared_ptr;
Member functions(constructor)Construct shared_ptr (public member function )(destructor)Destroy shared_ptr (public member function )operator=shared_ptr assignment (public member function )swapSwap content (public member function )resetReset pointer (public member function )getGet pointer (public member function )operator*Dereference object (public member function )operator->Dereference object member (public member function )use_countUse count (public member function )uniqueCheck if unique (public member function )operator boolCheck if not null (public member function )owner_beforeOwner-based ordering (public member function template )Non-member functionsOverloads:swapExchange content of shared_ptr objects (function template )relational operatorsRelational operators ==, !=, <, <=, >, >= (function template )ostream operator<

实例:

#include "shared_ptr.h"//class Foo {//public://	Foo() { std::cout << "Foo...\n"; }//	~Foo() { std::cout << "~Foo...\n"; }//};////struct D {//	void operator()(Foo* p) const {//		std::cout << "Call delete from function object...\n";//		delete p;//	}//};void func_shared_ptr(const shared_ptr
foo_){ cout << "foo_.use_count = " << foo_.use_count() << endl;}int stl_shared_ptr(){ std::shared_ptr
p1; { std::cout << "use_count:\n"; std::cout << "p1: " << p1.use_count() << '\n'; p1.reset(new int(0)); std::cout << "p1: " << p1.use_count() << '\n'; std::shared_ptr
p2(nullptr); std::cout << "p2: " << p2.use_count() << '\n'; std::shared_ptr
p3(new int); std::cout << "p3: " << p3.use_count() << '\n'; std::shared_ptr
p4(new int, std::default_delete
());//给出析构函数 std::cout << "p4: " << p4.use_count() << '\n'; std::shared_ptr
p5(new int, [](int* p){delete p; }, std::allocator
());//给出构造函数和析构函数 std::cout << "p5: " << p5.use_count() << '\n'; { std::shared_ptr
p6(p5);//调用拷贝构造函数 p6原本的减一,p5加一 std::cout << "p5in: " << p5.use_count() << '\n'; std::cout << "p6in: " << p6.use_count() << '\n'; } std::cout << "p5: " << p5.use_count() << '\n'; std::shared_ptr
p6(p5); std::shared_ptr
p7(std::move(p6));//左值变右值,p6失去对指针的权力 std::cout << "p5: " << p5.use_count() << '\n'; std::cout << "p6: " << p6.use_count() << '\n'; std::cout << "p7: " << p7.use_count() << '\n'; std::shared_ptr
p8(std::unique_ptr
(new int)); std::cout << "p8: " << p8.use_count() << '\n'; std::shared_ptr
obj(new C); std::shared_ptr
p9(obj, obj->data);//构造 std::cout << "p9: " << p9.use_count() << '\n'; } std::cout << "p3: " << p1.use_count() << '\n'; //考察一下引用传递对共享次数 指针的传递就会增加引用计数。 { auto foo_s_ptr = make_shared
(12);// 注意maked的使用 shared_ptr
foo_s_ptr2(foo_s_ptr); cout << "foo_s_ptr.use_count = " << foo_s_ptr.use_count() << endl; cout << "foo_s_ptr2.use_count = " << foo_s_ptr2.use_count() << endl; shared_ptr
foo_s_ptr3(foo_s_ptr); cout << "foo_s_ptr.use_count = " << foo_s_ptr.use_count() << endl; func_shared_ptr(foo_s_ptr2); cout << "foo_s_ptr.use_count = " << foo_s_ptr.use_count() << endl; } //类型 { std::cout << "constructor with no managed object\n"; std::shared_ptr
sh1; } //调用构造函数 { std::cout << "constructor with object\n"; std::shared_ptr
sh2(new Foo); std::shared_ptr
sh3(sh2); std::cout << sh2.use_count() << '\n'; std::cout << sh3.use_count() << '\n';} //注册deleter { std::cout << "constructor with object and deleter\n"; std::shared_ptr
sh4(new Foo, D()); std::shared_ptr
sh5(new Foo, [](Foo *p) { std::cout << "Call delete from lambda...\n"; delete p; }); } return 1;}

Class unique_ptr 

 不共享它的指针。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法。只能移动unique_ptr。这意味着,内存资源所有权将转移到另一 unique_ptr,并且原始 unique_ptr 不再拥有此资源。我们建议你将对象限制为由一个所有者所有,因为多个所有权会使程序逻辑变得复杂。因此,当需要智能指针用于纯 C++ 对象时,可使用 unique_ptr,而当构造 unique_ptr 时,可使用 Helper 函数。

拷贝一个std::unique_ptr将不被允许,因为如果你拷贝一个std::unique_ptr,那么拷贝结束后,这两个std::unique_ptr都会指向相同的资源,它们都认为自己拥有这块资源(所以都会企图释放)。

因此std::unique_ptr是一个仅能移动(move_only)的类型。

1.创建

unique_ptr不像shared_ptr一样拥有标准库函数make_shared来创建一个shared_ptr实例。要想创建一个unique_ptr,我们需要将一个new 操作符返回的指针传递给unique_ptr的构造函数。

int main(){    // 创建一个unique_ptr实例    unique_ptr
pInt(new int(5)); cout << *pInt;}

2、无法进行复制构造和赋值操作

unique_ptr没有copy构造函数,不支持普通的拷贝和赋值操作。

int main() {    // 创建一个unique_ptr实例    unique_ptr
pInt(new int(5)); unique_ptr
pInt2(pInt); // 报错 unique_ptr
pInt3 = pInt; // 报错}

3、可以进行移动构造和移动赋值操作

unique_ptr虽然没有支持普通的拷贝和赋值操作,但却提供了一种移动机制来将指针的所有权从一个unique_ptr转移给另一个unique_ptr。如果需要转移所有权,可以使用std::move()函数。

int main() {    unique_ptr
pInt(new int(5)); unique_ptr
pInt2 = std::move(pInt); // 转移所有权 //cout << *pInt << endl; // 出错,pInt为空 cout << *pInt2 << endl; unique_ptr
pInt3(std::move(pInt2));}

4、可以返回unique_ptr

unique_ptr不支持拷贝操作,但却有一个例外:可以从函数中返回一个unique_ptr。

unique_ptr
clone(int p){ unique_ptr
pInt(new int(p)); return pInt; // 返回unique_ptr}int main() { int p = 5; unique_ptr
ret = clone(p); cout << *ret << endl;}

5.unique_ptr使用场景

为动态申请的资源提供异常安全保证

返回函数内动态申请资源的所有权

在容器中保存指针

管理动态数组 

作为auto_ptr的替代品

实例 

#include "unique_ptr.h"#include 
#include
using namespace std;void f(const unique_Foo &){ std::cout << "f(const unique_Foo&)\n";}void TestAutoDestroy(){ //1. 普通的new对象. std::cout << "TestDestroy...................." << std::endl; { std::unique_ptr
p1(new unique_Foo); } //2. 普通的new[]对象. { std::unique_ptr
p2(new unique_Foo[4]);//unique_ptr数组模式 } //3. 自定义的deleter.和shared_ptr类似可以自己写一个析构函数,有多种方式 { std::unique_ptr
p3(new unique_Foo); std::unique_ptr
> p4(new unique_Foo, [](unique_Foo *p){ delete p; });//类似的lambda 表达式 }}void TestOwner(){ std::cout << "TestOwner...................." << std::endl; //1. new object. std::unique_ptr
p1(new unique_Foo); // p1 owns unique_Foo if (p1) p1->bar(); { std::unique_ptr
p2(std::move(p1)); // now p2 owns unique_Foo f(*p2); p1 = std::move(p2); // ownership returns to p1 p2->bar(); std::cout << "destroying p2...\n"; } p1->bar();}void TestArrayOwner(){ std::cout << "TestArrayOwner...................." << std::endl; //1. new[] object. std::unique_ptr
p1(new unique_Foo[4]); // p1 owns unique_Foo if (p1) p1[0].bar(); { std::unique_ptr
p2(std::move(p1)); // now p2 owns unique_Foo f(p2[0]); p1 = std::move(p2); // ownership returns to p1 p2[0].bar(); std::cout << "destroying p2...\n"; } p1[0].bar();}int stl_unique_ptr(){ TestAutoDestroy(); TestOwner(); TestArrayOwner(); return 0;}

weak_ptr 

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段. 

  weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少. 

std::weak_ptr 是一种智能指针,它对被  管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为 。

std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 ,此时如果原来的 被销毁,则该对象的生命期将被延长至这个临时的  同样被销毁为止。

此外,std::weak_ptr 还可以用来避免  的循环引用。

weak_ptr 在功能上类似于普通指针, 然而一个比较大的区别是, 弱引用能检测到所管理的对象是否已经被释放, 从而避免访问非法内存。

构造新的weak_ptr 
(公开成员函数)

销毁 weak_ptr 
(公开成员函数)

weak_ptr赋值 
(公开成员函数)

修改器

释放被管理对象的所有权 
(公开成员函数)

交换被管理对象 
(公开成员函数)

观察器

返回管理该对象的 shared_ptr 对象数量 
(公开成员函数)

检查被引用的对象是否已删除 
(公开成员函数)

创建管理被引用的对象的shared_ptr 
(公开成员函数)

提供弱指针的基于拥有者顺序 
(公开成员函数)

非成员函数

(C++11)

特化  算法 
(函数模板)

辅助类

(C++20)

原子弱指针 
(类模板特化)

weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.

expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.

lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
use_count 返回与 shared_ptr 共享的对象的引用计数.
reset 将 weak_ptr 置空.
weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.

创建及使用

#include "weak_ptr.h"using namespace std;class CB;class CA;class CA{public:	CA(){}	~CA(){  }	void Register(const std::shared_ptr
& sp) { m_spb = sp; cout << "Register_spib.use_count = " << sp.use_count() << endl; }private: std::weak_ptr
m_spb;//这样不会增加m_spb的引用计数 ,那么CA就能释放};class CB{public: CB(){}; ~CB(){ }; void Register(const std::shared_ptr
& sp) { m_spa = sp; cout << "Register_spia.use_count = " << sp.use_count() << endl; }private: std::shared_ptr
m_spa;//与上条注释的差别所在,无论什么情况下,B的智能指针的释放都得等m_spa的引用计数为1.};std::weak_ptr
gw;void f(){ if (auto spt = gw.lock()) { // 使用之前必须复制到 shared_ptr,且会增加原来的shared_ptr的计数 std::cout << *spt << "\n"; cout << "spt.use_count = " << spt.use_count() << endl; } else { std::cout << "gw is expired\n"; }}void weak_ptr_lock(){ { auto sp = std::make_shared
(42); cout << "sp.use_count = " << sp.use_count() << endl; gw = sp; f(); cout << "sp.use_count = " << sp.use_count() << endl;//使用计数恢复到1 } f();}void stl_weak_circle(){ std::shared_ptr
spa(new CA); std::shared_ptr
spb(new CB); std::weak_ptr
weak_cb = spb;//并不改变计数 std::weak_ptr
weak_cb1(spb);//并不改变计数 cout << "spb.use_count = " << spb.use_count() << endl; cout << "spa.use_count = " << spa.use_count() << endl; spb->Register(spa); spa->Register(spb); cout << "spb.use_count = " << spb.use_count() << endl; cout << "spa.use_count = " << spa.use_count() << endl;}void stl_weak_ptr(){ weak_ptr_lock(); stl_weak_circle();}

 

转载地址:https://blog.csdn.net/u012516571/article/details/82750999 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:shell总结
下一篇:C++ 标准库读书杂记6 Tuple

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年08月20日 11时09分03秒