详解C++11智能指针

前言

C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用。

C++11智能指针介绍

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。

为什么要使用智能指针

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针就是一个类,当超出了类的作用域是,类会自动调用析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。

auto_ptr

(C++98的方案,C++11已经抛弃)采用所有权模式。

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud."));
auto_ptr<string> p2;
p2 = p1; //auto_ptr不会报错.

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

unique_ptr

(替换auto_ptr)unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。

采用所有权模式,还是上面那个例子

unique_ptr<string> p3 (new string ("auto"));   //#4
unique_ptr<string> p4;                       //#5
p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。尝试复制p3时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患。因此,unique_ptr比auto_ptr更安全。

另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr<string> pu1(new string ("hello world"));
unique_ptr<string> pu2;
pu2 = pu1;                                      // #1 不允许
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string ("You"));   // #2 允许

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。

额外:boost库的boost::scoped_ptr也是一个独占性智能指针,但是它不允许转移所有权,从始而终都只对一个资源负责,它更安全谨慎,但是应用的范围也更狭窄。)

例如:

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数:

use_count 返回引用计数的个数

unique 返回是否是独占所有权( use_count 为 1)

swap 交换两个 shared_ptr 对象(即交换所拥有的对象)

reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少

get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如

shared_ptr<int> sp(new int(1));

sp 与 sp.get()是等价的。

share_ptr的简单例子:

int main()
{string *s1 = new string("s1");shared_ptr<string> ps1(s1);shared_ptr<string> ps2;ps2 = ps1;cout << ps1.use_count()<<endl;   //2cout<<ps2.use_count()<<endl; //2cout << ps1.unique()<<endl;  //0string *s3 = new string("s3");shared_ptr<string> ps3(s3);cout << (ps1.get()) << endl;   //033AEB48cout << ps3.get() << endl;    //033B2C50swap(ps1, ps3);   //交换所拥有的对象cout << (ps1.get())<<endl;    //033B2C50cout << ps3.get() << endl;    //033AEB48cout << ps1.use_count()<<endl;    //1cout << ps2.use_count() << endl; //2ps2 = ps1;cout << ps1.use_count()<<endl;    //2cout << ps2.use_count() << endl; //2ps1.reset(); //放弃ps1的拥有权,引用计数的减少cout << ps1.use_count()<<endl;    //0cout << ps2.use_count()<<endl;   //1
}

weak_ptr

share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

class B; //声明
class A
{
public:shared_ptr<B> pb_;~A(){cout << "A delete\n";}
};class B
{
public:shared_ptr<A> pa_;~B(){cout << "B delete\n";}
};void fun()
{shared_ptr<B> pb(new B());shared_ptr<A> pa(new A());cout << pb.use_count() << endl;    //1cout << pa.use_count() << endl;  //1pb->pa_ = pa;pa->pb_ = pb;cout << pb.use_count() << endl;    //2cout << pa.use_count() << endl;  //2
}int main()
{fun();return 0;
}

可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A、B的析构函数没有被调用)运行结果没有输出析构函数的内容,造成内存泄露。如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_,改为weak_ptr pb_ ,运行结果如下:

1
1
1
2
B delete
A delete

这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。

注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:

shared_ptr<B> p = pa->pb_.lock();
p->print();

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 内部对象的计数.

share_ptr和weak_ptr的核心实现

weakptr的作为弱引用指针,其实现依赖于counter的计数器类和share_ptr的赋值,构造,所以先把counter和share_ptr简单实现

Counter简单实现

class Counter
{
public:Counter() : s(0), w(0){};int s;  //share_ptr的引用计数int w;  //weak_ptr的引用计数
};

counter对象的目地就是用来申请一个块内存来存引用基数,s是share_ptr的引用计数,w是weak_ptr的引用计数,当w为0时,删除Counter对象。

share_ptr的简单实现

template <class T>
class WeakPtr; //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用template <class T>
class SharePtr
{
public:SharePtr(T *p = 0) : _ptr(p){cnt = new Counter();if (p)cnt->s = 1;cout << "in construct " << cnt->s << endl;}~SharePtr(){release();}SharePtr(SharePtr<T> const &s){cout << "in copy con" << endl;_ptr = s._ptr;(s.cnt)->s++;cout << "copy construct" << (s.cnt)->s << endl;cnt = s.cnt;}SharePtr(WeakPtr<T> const &w) //为了用weak_ptr的lock(),来生成share_ptr用,需要拷贝构造用{cout << "in w copy con " << endl;_ptr = w._ptr;(w.cnt)->s++;cout << "copy w  construct" << (w.cnt)->s << endl;cnt = w.cnt;}SharePtr<T> &operator=(SharePtr<T> &s){if (this != &s){release();(s.cnt)->s++;cout << "assign construct " << (s.cnt)->s << endl;cnt = s.cnt;_ptr = s._ptr;}return *this;}T &operator*(){return *_ptr;}T *operator->(){return _ptr;}friend class WeakPtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值protected:void release(){cnt->s--;cout << "release " << cnt->s << endl;if (cnt->s < 1){delete _ptr;if (cnt->w < 1){delete cnt;cnt = NULL;}}}private:T *_ptr;Counter *cnt;
};

share_ptr的给出的函数接口为:构造,拷贝构造,赋值,解引用,通过release来在引用计数为0的时候删除_ptr和cnt的内存。

weak_ptr简单实现

template <class T>
class WeakPtr
{
public: //给出默认构造和拷贝构造,其中拷贝构造不能有从原始指针进行构造WeakPtr(){_ptr = 0;cnt = 0;}WeakPtr(SharePtr<T> &s) : _ptr(s._ptr), cnt(s.cnt){cout << "w con s" << endl;cnt->w++;}WeakPtr(WeakPtr<T> &w) : _ptr(w._ptr), cnt(w.cnt){cnt->w++;}~WeakPtr(){release();}WeakPtr<T> &operator=(WeakPtr<T> &w){if (this != &w){release();cnt = w.cnt;cnt->w++;_ptr = w._ptr;}return *this;}WeakPtr<T> &operator=(SharePtr<T> &s){cout << "w = s" << endl;release();cnt = s.cnt;cnt->w++;_ptr = s._ptr;return *this;}SharePtr<T> lock(){return SharePtr<T>(*this);}bool expired(){if (cnt){if (cnt->s > 0){cout << "empty" << cnt->s << endl;return false;}}return true;}friend class SharePtr<T>; //方便weak_ptr与share_ptr设置引用计数和赋值protected:void release(){if (cnt){cnt->w--;cout << "weakptr release" << cnt->w << endl;if (cnt->w < 1 && cnt->s < 1){//delete cnt;cnt = NULL;}}}private:T *_ptr;Counter *cnt;
};

weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。

详解C++11智能指针相关推荐

  1. C++智能指针详解【C++智能指针】

    自动内存管理 智能指针 什么是 RAII 原理 智能指针的模板(template)实现 auto_ptr auto_ptr 使用 重载函数 operator-> / *语法格式 自实现 auto ...

  2. C++11智能指针(unique_ptr、shared_ptr、weak_ptr)boost::scoped_ptr

    C++11智能指针(unique_ptr.shared_ptr.weak_ptr)_-码农小非-的专栏-CSDN博客_c++ shared_ptr weak_ptr 原创)智能指针拾遗 (原创)智能指 ...

  3. 区块链技术进阶-深入详解以太坊智能合约语言 solidity(含源码)-熊丽兵-专题视频课程...

    区块链技术进阶-深入详解以太坊智能合约语言 solidity(含源码)-103人已学习 课程介绍         区块链开发技术进阶-深入详解以太坊智能合约语言 solidity视频培训教程:本课程是 ...

  4. kernel_mktime() 详解 —— Linux-0.11 学习笔记(四)

    题目:kernel_mktime() 详解 -- Linux-0.11 学习笔记(四) 在init/main.c文件中,有一个函数static void time_init(void) 该函数读取 C ...

  5. 【C++11新特性】 C++11智能指针之weak_ptr

    http://blog.csdn.net/xiejingfa/article/details/50772571 原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/ar ...

  6. c++11新特性_【C++11新特性】 C++11智能指针之weak_ptr

    如题,我们今天要讲的是 C++11 引入的三种智能指针中的:weak_ptr. 在学习 weak_ptr 之前最好对 shared_ptr 有所了解.如果你还不知道 shared_ptr 是何物,可以 ...

  7. javacv开发详解之11补充篇:使用批量多张图片制作gif动态图,再也不用担心不会制作动态图了

    javacv实战专栏目录: JavaCV实战专栏文章目录(JavaCV速查手册) 前言 前面写过视频.桌面屏幕录制apng和gif动态图,虽然已经满足大部分情况,但是没考虑到现在很多动态图都是使用多张 ...

  8. 技术点详解---双链路智能切换--h3c NQA(Network Quality Analysis)

    技术点详解---双链路智能切换 在上一期中,我们介绍了非常给力的双链路网吧解决方案,但在实际使用过程中,仍然有很多网吧老板抱怨. 一.     网吧老板的抱怨 这位淡定的网吧老板是这样反馈问题的: 1 ...

  9. C++11 智能指针时要避开的 10 大错误

    我很喜欢C++11全新的智能指针,对于那些苦恼于自己管理内存的开发者来说,这是天大的好事.但是,在广泛使用C++11智能指针的两年里,因为使用方法不当而导致程序的效率下降甚至直接crash掉的事情发生 ...

最新文章

  1. 标识位:可以用以确定鼠标点击的是哪一个按钮或者是文本框
  2. Spark(1)——spark基本原理与启动
  3. 5G PDCCH介绍
  4. Google Drive客户端
  5. 陕西省高级职称 计算机要求,陕西省卫生高级职称评审申报条件
  6. [Luogu 1351] NOIP2014 联合权值
  7. iOS:图片相关(19-05-09更)
  8. python 数组去重复_numpy数组去掉重复的行,保留唯一的行数据
  9. 【软件工程】第一次阅读作业
  10. java搭建环境配置_JAVA开发环境的搭建(配置JAVA开发环境)
  11. python主要数据变量_python的数据类型和变量
  12. dotnetbar 5.8.0.3
  13. 最优化方法:拉格朗日乘数法(转)
  14. Pr对图像元素进行裁剪
  15. 三十五 我在软件园的那些日子里
  16. java yyyy m d_JAVA SimpleDateFormat使用YYYY-MM-dd的坑
  17. 不改一行代码!快速迁移 Koa 应用上云
  18. 完全二叉树和满二叉树
  19. 爬虫遭遇状态码521的方法
  20. javaScript:实现倒计时定时器setInterval的开始计时、暂停计时、停止计时效果

热门文章

  1. 操作系统各大公司笔试题汇总
  2. 文件内容批量修改工具
  3. APScheduler —— Python化的Cron
  4. 【分享】 自闭症儿童网络画展 - JS效果
  5. WEB前端技术趋势图示-JS库
  6. 利用自定义分页技术提高数据库性能
  7. [基础题] 7.第二种(*)按如下要求编写Java程序:
  8. oracle表格颜色,如何在oracle中使用光标更新特定颜色
  9. Algorithm Master Road:算法的时间/空间复杂度
  10. 《数据库原理与应用》(第三版)第11章 存储过程和触发器 基础 习题参考答案