c++ 之四大智能指针 std::auto_ptr std::shared_ptr std::unuque std::weak_ptr 比较总结

1. 动态内存必要性

程序不知道自己需要多少对象; 
程序不知道对象的准确类型; 
程序需要在多个对象之间共享数据;

2. 动态内存在哪里

程序有静态内存、栈内存。静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建或销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。 
除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式的销毁它们。(c++ primer P400)

3. 自由存储区和堆

自由存储是c++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区 
堆是操作系统维护的一块内存 
虽然c++编译器默认使用堆来实现自由存储。但两者不能等价

4. 动态内存与智能指针

我们知道c++需要注意的地方之一就是对内存的管理,动态内存的使用经常会出现内存泄漏,或者产生引用非法内存的指针 
c++11标准库提供了三种智能指针类型来管理动态对象:

智能指针:自动负责释放所指向的对象,实际上它利用了栈的机制,每一个智能指针都是一个模板类,调用智能指针实际上是创建了一个智能指针的对象,对象生命周期到达尽头的时候,会自动调用智能指针的析构函数,在析构函数里,释放掉它管理的内存,从而避免手动delete。 

  1. shared_ptr 允许多个指针指向同一个对象 
  2. unique_ptr 独占所指向的对象
  3. weak_ptr  shared_ptr的弱引用 
  4. auto_ptr c++98提出 c++11 摒弃 对于特定对象,和unique_ptr一样只能被一个智能指针所拥有,这样,只有拥有该对象的智能指针的析构函数才会删除该对象

定义在memory头文件中,他们的作用在于会自动释放所指向的对象

5. 智能指针的详细描述

5.1 auto_ptr描述

auto_ptr主要是用来解决资源自动释放的问题,比如如下代码:


 
 
  1. void Function()
  2. {
  3. Obj*p = new Obj( 20);
  4. ...
  5. if (error occor)
  6. throw ... 或者 retrun;
  7. delete p;
  8. }

在函数遇到错误之后,一般会抛异常,或者返回,但是这时很可能遗漏之前申请的资源,及时是很有经验的程序员也有可能出现这种错误.
而使用auto_ptr会在自己的析够函数中进行资源释放。也就是所说的RAII
使用auto_ptr代码如下


 
 
  1. void Function()
  2. {
  3. auto_ptr<Obj> ptr( new Obj( 20) );
  4. ...
  5. if (error occur)
  6. throw exception...
  7. }

这样无论函数是否发生异常,在何处返回,资源都会自动释放。
需要提一下的是这是一个被c++11标准废弃的一个智能指针,为什么会被废弃,先看一下下面的代码:


 
 
  1. auto_ptr<Obj> ptr1( new Obj() );
  2. ptr1->FuncA();
  3. auto_ptr<Obj> ptr2 = ptr1;
  4. ptr2->FuncA();
  5. ptr1->FuncA();   // 这句话会异常

为什么在把ptr1复制给ptr2之后ptr1再使用就异常了呢?
这也正是他被抛弃的主要原因。
因为auto_ptr复制构造函数中把真是引用的内存指针进行的转移,也就是从ptr1转移给了ptr2,此时,ptr2引用了Obj内存地址,而ptr1引用的内存地址为空,此时再使用ptr1就异常了。

5.2 shared_ptr描述(in memory):

shared_ptr是一个标准的共享所有权的智能指针,就是允许多个指针指向同一对象,shared_ptr对象中不仅有一个指针指向某某(比如 int型,以下也拿int类型举例)对象,还拥有一个引用计数器,代表一共有多少指针指向了那个对象。

为什么shared_ptr允许多个指针指向同一对象?
因为 动态对象的所有权不确定。对象可以在多个作用域中共享,又不能像栈对象一样自由地值拷贝。只要有一个对象\作用域还持有这个动态对象,他就不能销毁,当他没有用时,自动销毁。

shared_ptr自动销毁所管理的对象

每当创建一个shared_ptr的对象指向int型数据,则引用计数器值+1,每当销毁一个shared_ptr对象,则-1.当引用计数器数据为0时,shared_ptr的析构函数会销毁int型对象,并释放它占用的内存。

shared_ptr和new的配合使用

接受指针作为参数的智能指针的构造函数是explicit类型,意味着必须使用直接初始化,不能做隐式类型转换 


 
 
  1. shared_ptr< int> p1;
  2. //被初始化成为一个空指针
  3. shared_ptr< int> p2 ( new int( 4));
  4. //指向一个值是4的int类型数据
  5. shared_ptr< int> p3 = new int( 4);
  6. //错误,必须直接初始化

 
 
  1. shared_ptr< int> Fun( int p)
  2. {
  3. return new int(p); //error
  4. return shared_ptr< int>( new int(p)); //right
  5. }


不能混合使用普通指针和智能指针,因为智能指针不是单纯的赤裸裸的指针


 
 
  1. void process(shared_ptr<int> ptr){
  2. //受到参数值传递的影响,ptr被构造并且诞生,执行完函数块后被释放
  3. }
  4. int *x( new int ( 43));
  5. //x是一个普通的指针
  6. process(x);
  7. //错误,int * 不能转换成shared_ptr<int>类型
  8. process(shared_ptr< int> x);
  9. //临时创造了x,引用数+1,执行完process之后,引用数-1
  10. int j = *x;
  11. //x是一个空悬指针,是未定义的


不能使用get()函数对智能指针赋值或初始化

原因:get()函数得到的是共享对象的地址,是内置指针,指向智能指针管理的对象,而智能指针不仅仅包含地址,两个东西不是一个类型的,也不能彼此包含,因此不能这样做。 
同样,把get()返回值 绑定到智能指针上也是错误的 
如下:


 
 
  1. shared_ptr< int> p ( new int( 22));
  2. int *q = p.get();
  3. //语义没问题
  4. {
  5. shared_ptr< int> (q);
  6. //意味着q被绑定,!!!!引用计数器还是1!!!!
  7. //如果这个被执行,程序块结束以后q和q所指的内容被销毁,则代表着以后执行(*p)的解引用操作,就成了未定义的了。
  8. }
  9. int r = *p;
  10. //已经不对了,因为p指向的内存已经在刚才那个代码块里被q释放了

shared_ptr的一些操作


 
 
  1. shared_ptr< T> p;
  2. //空智能指针,可指向类型是T的对象
  3. if(p)
  4.   //如果p指向一个对象,则是true
  5. (*p)
  6. //解引用获取指针所指向的对象
  7. p -> number == (*p).number;
  8. p. get();
  9. //返回p中保存的指针
  10. swap(p,q);
  11. //交换p q指针
  12. p. swap(q);
  13. //交换p,q指针
  14. make_shared< T>(args) 
  15. //返回一个shared_ptr的对象,指向一个动态类型分配为T的对象,用args初始化这个T对象
  16. shared_ptr< T> p(q)
  17. //p 是q的拷贝,q的计数器++,这个的使用前提是q的类型能够转化成是T*
  18. shared_pts< T> p(q,d) 
  19. //p是q的拷贝,p将用可调用对象d代替delete
  20. //上面这个我其实没懂,也没有查出来这个的意思
  21. p =q;
  22. //p的引用计数-1,q的+1,p为零释放所管理的内存
  23. p.unique();
  24. //判断引用计数是否是1,是,返回true
  25. p.use_count();
  26. //返回和p共享对象的智能指针数量
  27. p.reset();
  28. p.reset(q);
  29. p.reset(q,d);
  30. //reset()没懂,这个以后再来补充吧

shared_ptr 强引用和弱引用

强引用和弱引用就是shared_ptr用来维护引用计数的信息

  • 强引用 

用来记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).

  • 弱引用 

用来记录当前有多少个正在观察该对象的 weak_ptrs. 当最后一个弱引用离开的时候, 共享的内部信息控制块会被销毁和释放 (共享的对象也会被释放, 如果还没有释放的话).

  • 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象
  • 当指向一个对象的最后一个shared_ptr被销毁,shared_ptr类会自通过调用对应的析构函数销毁此对象  
  • shared_ptr会自动释放相关联的内存

 
 
  1. //该函数返回一个T类型的动态分配的对象,对象是通过一个类型为Q的参数来进行初始化的
  2. shared_ptr<T> Fun(Q arg)
  3. {
  4. //对arg进行处理
  5. //shared_ptr负责释放内存
  6. return make_shared<T>(arg);
  7. //由于返回的是shared_ptr,我们可以保证他分配的对象会在恰当的时候释放
  8. }
  9. void use_Fun(Q arg)
  10. {
  11. shared_ptr<T> p = Fun(arg);
  12. //使用p
  13. }
  14. //函数结束,p离开了作用域,他指向的内存被自动释放
  15. shared_ptr<T> use_Fun(Q arg)
  16. {
  17. shared_ptr<T> p = Fun(arg);
  18. //使用p
  19. return p; //返回p时,引用计数递增
  20. }
  21. //p离开了作用域,但他不会释放指向的内存

shared_ptr 测试例子


 
 
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <iostream>
  3. #include <string>
  4. #include <memory>
  5. #include <vector>
  6. #include <map>
  7. void mytest()
  8. {
  9. std:: shared_ptr< int> sp1( new int( 22));
  10. std:: shared_ptr< int> sp2 = sp1;
  11. std:: cout << "cout: " << sp2.use_count() << std:: endl; // 打印引用计数
  12. std:: cout << *sp1 << std:: endl;
  13. std:: cout << *sp2 << std:: endl;
  14. sp1.reset(); // 显示让引用计数减一
  15. std:: cout << "count: " << sp2.use_count() << std:: endl; // 打印引用计数
  16. std:: cout << *sp2 << std:: endl; // 22
  17. return;
  18. }
  19. int main()
  20. {
  21. mytest();
  22. system( "pause");
  23. return 0;
  24. }


5.3 unique_ptr描述(in memory):

unique_ptr的直观认知应该就是“独占”、“拥有”,与shared_ptr不同,某一时刻,只能有一个unique_ptr指向一个给定的对象即不能拷贝和赋值。因此,当unique_ptr被销毁,它所指的对象也会被销毁。

 unique_ptr的“独占”是指:不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。例如:


 
 
  1. std:: unique_ptr< int> p ( new int);
  2. std:: unique_ptr< int> q = p; //error


但是unique_ptr允许通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,注意,这时它本身就不再拥有原来指针的所有权了。后面都会提到

unique_ptr的初始化必须采用直接初始化


 
 
  1. unique_ptr< string> p( new string( "China"));
  2. //没问题
  3. unique_ptr< string> p (q);
  4. //错误,不支持拷贝
  5. unique_ptr< string> q;
  6. q = p;
  7. //错误,不支持赋值
  8. unique_ptr的一些操作:
  9. unique_ptr<T> p;
  10. //空智能指针,可指向类型是T的对象
  11. if(p) 
  12. //如果p指向一个对象,则是true
  13. (*p)
  14. //解引用获取指针所指向的对象
  15. p -> number == (*p).number;
  16. p.get();
  17. //返回p中保存的指针
  18. swap(p,q);
  19. //交换p q指针
  20. p.swap(q);
  21. //交换p,q指针
  22. unique_ptr<T,D>p;
  23. //p使用D类型的可调用对象释放它的指针
  24. p = nullptr;
  25. //释放对象,将p置空
  26. p.release();
  27. //p放弃对指针的控制,返回指针,p置数空
  28. p.reset();
  29. //释放p指向的对象
  30. p.reset(q);
  31. //让u指向内置指针q

unique_ptr作为参数传递和返回值,是可以拷贝或者赋值的

unique_ptr的不能拷贝有一个例外:


 
 
  1. unique_ptr< int> Fun( int p) 
  2. {
  3.     return unique_ptr< int>( new int(p));
  4. }
  5. //返回一个局部对象的拷贝
  6. unique_ptr< int> Fun( int p) 
  7. {
  8.     unique_ptr< int> ret( new int(p));
  9.     //...
  10.     return ret;
  11. }

编译器知道要返回的对象将要被销毁,执行了一种特殊而“拷贝”(移动操作) 

如何安全的重用unique_ptr指针

要安全的重用unique_ptr指针,可给它赋新值。C++为其提供了std::move()方法。


 
 
  1.     unique_ptr< string> pu1( new string( "nihao"));
  2.     unique_ptr< string> pu2;
  3.     pu2 = std::move(pu1); //move
  4.     cout<<*pu1<< endl; //赋新值

比较而言auto_ptr由于策略没有unique_ptr严格,无需使用move方法


 
 
  1.     auto_ptr< string> pu1, pu2;
  2.     pu1 = demo2( "Uniquely special");
  3.     pu2 = pu1;
  4.     pu1 = demo2( " and more");
  5.     cout<<*pu2<<*pu1<< endl;

由于unique_ptr使用了C++11新增的移动构造函数和右值引用,所以可以区分安全和不安全的用法。

unique_ptr相较auto_ptr提供了可用于数组的变体

auto_ptr和shared_ptr可以和new一起使用,但不可以和new[]一起使用,但是unique_ptr可以和new[]一起使用


 
 
  1. unique_ptr< double[]> pda( new double( 5));
  2. pda.release();
  3. //自动用delete[]销毁其指针释放内存

unique_ptr 测试例子


 
 
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <iostream>
  3. #include <string>
  4. #include <memory>
  5. #include <vector>
  6. #include <map>
  7. void mytest()
  8. {
  9. std:: unique_ptr< int> up1( new int( 11)); // 无法复制的unique_ptr
  10. //unique_ptr<int> up2 = up1; // err, 不能通过编译
  11. std:: cout << *up1 << std:: endl; // 11
  12. std:: unique_ptr< int> up3 = std::move(up1); // 现在p3是数据的唯一的unique_ptr
  13. std:: cout << *up3 << std:: endl; // 11
  14. //std::cout << *up1 << std::endl; // err, 运行时错误
  15. up3.reset(); // 显式释放内存
  16. up1.reset(); // 不会导致运行时错误
  17. //std::cout << *up3 << std::endl; // err, 运行时错误
  18. std:: unique_ptr< int> up4( new int( 22)); // 无法复制的unique_ptr
  19. up4.reset( new int( 44)); //"绑定"动态对象
  20. std:: cout << *up4 << std:: endl; // 44
  21. up4 = nullptr; //显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价
  22. std:: unique_ptr< int> up5( new int( 55));
  23. int *p = up5.release(); //只是释放控制权,不会释放内存
  24. std:: cout << *p << std:: endl;
  25. //cout << *up5 << endl; // err, 运行时错误
  26. delete p; //释放堆区资源
  27. return;
  28. }
  29. int main()
  30. {
  31. mytest();
  32. system( "pause");
  33. return 0;
  34. }

5.4 weak_ptr描述(in memory)

weak_ptr是一种不控制所指向对象生存期的智能指针,指向shared_ptr管理的对象,但是不影响shared_ptr的引用计数。它像shared_ptr的助手,一旦最后一个shared_ptr被销毁,对象就被释放,weak_ptr不影响这个过程。

  • weak_ptr是为配合shared_ptr而引入的一种智能指针来协助shared_ptr工作,它可以从一个shared_ptr或另一个weak_ptr对象构造,它的构造和析构不会引起引用计数的增加或减少。没有重载 * 和 -> 但我们可以通过lock来获得一个shared_ptr对象来对资源进行使用,如果引用的资源已经释放,lock()函数将返回一个存储空指针的shared_ptr。 expired函数用来判断资源是否失效。
  • weak_ptr的使用更为复杂一点,它可以指向shared_ptr指针指向的对象内存,却并不拥有该内存,而使用weak_ptr成员lock,则可返回其指向内存的一个share_ptr对象,且在所指对象内存已经无效时,返回指针空值nullptr。

注意:weak_ptr并不拥有资源的所有权,所以不能直接使用资源。
可以从一个weak_ptr构造一个shared_ptr以取得共享资源的所有权。

weak_ptr的一些操作:


 
 
  1. weak_ptr<T> w(sp);
  2. //定义一个和shared_ptr sp指向相同对象的weak_ptr w,T必须能转化成sp指向的类型
  3. w = p;
  4. //p是shared_ptr或者weak_ptr,w和p共享对象
  5. w.reset();
  6. //w置为空
  7. w.use_count();
  8. //计算与w共享对象的shared_ptr个数
  9. w.expired();
  10. //w.use_count()为0,返回true
  11. w. lock();
  12. //w.expired()为true,返回空shared_ptr,否则返回w指向对象的shared_ptr

weak_ptr 测试例子


 
 
  1. #define _CRT_SECURE_NO_WARNINGS
  2. #include <iostream>
  3. #include <string>
  4. #include <memory>
  5. #include <vector>
  6. #include <map>
  7. void check(std::weak_ptr<int> &wp)
  8. {
  9. std:: shared_ptr< int> sp = wp.lock(); // 转换为shared_ptr<int>
  10. if (sp != nullptr)
  11. {
  12. std:: cout << "still: " << *sp << std:: endl;
  13. }
  14. else
  15. {
  16. std:: cout << "still: " << "pointer is invalid" << std:: endl;
  17. }
  18. }
  19. void mytest()
  20. {
  21. std:: shared_ptr< int> sp1( new int( 22));
  22. std:: shared_ptr< int> sp2 = sp1;
  23. std::weak_ptr< int> wp = sp1; // 指向shared_ptr<int>所指对象
  24. std:: cout << "count: " << wp.use_count() << std:: endl; // count: 2
  25. std:: cout << *sp1 << std:: endl; // 22
  26. std:: cout << *sp2 << std:: endl; // 22
  27. check(wp); // still: 22
  28. sp1.reset();
  29. std:: cout << "count: " << wp.use_count() << std:: endl; // count: 1
  30. std:: cout << *sp2 << std:: endl; // 22
  31. check(wp); // still: 22
  32. sp2.reset();
  33. std:: cout << "count: " << wp.use_count() << std:: endl; // count: 0
  34. check(wp); // still: pointer is invalid
  35. return;
  36. }
  37. int main()
  38. {
  39. mytest();
  40. system( "pause");
  41. return 0;
  42. }

6. 如何选择智能指针:


 
 
  1. #include <iostream>
  2. #include <memory>
  3. #include <vector>
  4. #include <algorithm>
  5. #include <stdlib.h>
  6. using namespace std;
  7. unique_ptr< int> make_int( int n)
  8. {
  9. return unique_ptr< int> ( new int(n));
  10. }
  11. void show(unique_ptr<int> & pi) //pass by reference
  12. {
  13. cout<< *pi << ' ';
  14. }
  15. int main()
  16. {
  17. vector< unique_ptr< int> > vp( 5);
  18. for( int i = 0; i < vp.size(); ++i)
  19. {
  20. vp[i] = make_int(rand() % 1000); //copy temporary unique_ptr
  21. }
  22. vp.push_back(make_int(rand() % 1000)); //ok because arg is temporary
  23. for_each(vp.begin(), vp.end(), show);
  24. unique_ptr< int> pup(make_int(rand() % 1000));
  25. // shared_ptr<int> spp(pup);//not allowed. pup is lvalue
  26. shared_ptr< int> spr(make_int(rand() % 1000));
  27. return 0;
  28. }

总结:

  1. 当多个对象指向同一个对象的指针时,应选择shared_ptr 
  2. 用new申请的内存,返回指向这块内存的指针时,选择unique_ptr就不错 
  3. 在满足unique_ptr要求的条件时,前提是没有不明确的赋值,也可以使用auto_ptr 
  4. 如上述代码所示,unique_ptr为右值(不准确的说类似无法寻址)时,可以赋给shared_ptr 
  5. 尽量使用unique_ptr而不要使用auto_ptr
  6. 一般来说shared_ptr能够满足我们大部分的需求
  7. weak_ptr可以避免递归的依赖关系

 
参考链接:


https://blog.csdn.net/derkampf/article/details/72654883 

https://blog.csdn.net/weixin_36888577/article/details/80188414 

https://blog.csdn.net/zhuziyu1157817544/article/details/64927834 

https://blog.csdn.net/zsc_976529378/article/details/52250597

https://www.cnblogs.com/lsgxeva/p/7788061.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值