赞
踩
智能指针我也是看过了很多文章,也自己尝试了自己去设计和实现,但是一直有一个问题存疑在我心中——那就是我们为什么要使用智能指针。我既然能够很好地使用new和delete去管理我的堆上内存,也留意了拷贝构造函数的浅拷贝问题,似乎也就没有智能指针的用武之地了。如果有同样想法的同学,应该好好看看我这篇博文。
最近在复习设计模式,用C++去写一些设计模式的代码,然后就想到了一些问题:设计模式肯定是要用到多态的,而C++的多态是用指针或引用来实现的(基本上是用指针),但是如果用的是原生指针,那对象的生命周期该如何管理?很显然,我们不能再用以前手动new / delete的方式去维护对象的生命周期,因为我们无法得知对象应该在什么时候要被析构掉。在各种设计模式中,一个对象不再是个独立的个体,他可能会和许多的对象产生关系,所以就很难维护一个个体的生命周期。
然后这几天在看陈硕的《Linux多线程服务端编程:使用muduo C++网络库》,里面的第一章就着重讲了多线程中对象的生命周期管理。设计模式的对象生命周期管理还算简单,但是这个问题一旦放到多线程中,就变成了一个更加复杂且麻烦的事情。多线程的不确定性大大提高了对象生命周期的维护难度,使用原生指针很容易就会造成空悬指针的问题(浅拷贝造成的问题也是空悬指针)。比起内存泄漏,这是个更加严重的问题,使用空悬指针就跟使用了野指针一样,直接让程序crash掉。
所以我们希望有一种代理,能够帮我们管理指针所指向的对象的生命周期,同时使用的时候也像普通指针一样去使用。于是乎就有了智能指针这一伟大发明。
智能指针因为其优秀的设计和不俗的性能,被纳入了C++11的标准库中,也是每个C++程序员必须掌握的技能之一。
我们通常所说的智能指针,其实就是以下几种封装好的模板类:
除了第一个,后面的都是C++11引入的,使用时需要#include <memory>。这里主要介绍shared_ptr和weak_ptr,他们两个搭配使用就能解决大部分情况的对象生命周期管理问题。其他指针的详细内容可以去翻阅文档。
然后智能指针有以下三种特点:
RAII机制(Resource Acquisition Is Initialization),资源获取即初始化。 这是智能指针的核心思想,也是C++程序设计的重要思想。这里引用陈硕书中的一句话:
初学C++的教条是“new和delete要配对,new了之后要记者delete”;如果使用RAII,要改成“每一个明确的资源配置动作(例如new)都应该在单一语句用执行,并在该语句中立刻将配置获得的资源交给handle对象(如shared_ptr),程序中一般不出现delete”。
综合特点与机制,可以推测出智能指针的设计大概是这样的:
所以智能指针本质上就是一个模板类,他封装了T类型的对象指针,有构造函数,有拷贝构造函数,有析构函数,能重载各种运算符。所以利用好我们以前学的知识,我们自己也能封装这些智能指针。
不同的智能指针有不同的资源管理方式,我们要依情况来选择不同的智能指针。大体流程就是在智能指针构造的时候,“托付”一个堆上对象指针给他管理;然后利用对象在离开作用域的时候会调用析构函数这一机制,做到有效地管理对象生命周期,我可以在智能指针的析构函数中delete掉他管理的堆上对象指针嘛。下面我们来讨论shared_ptr和weak_ptr是怎么实现的。
shared_ptr实现了共享拥有的概念,利用“引用计数”来控制堆上对象的生命周期。 原理也很简单,在初始化的时候引用计数设为1,每当被拷贝或者赋值的时候引用计数+1,析构的时候引用计数-1,直到引用计数被减到0,那么就可以delete掉对象的指针了。他的构造方式主要有以下三种:
shared_ptr<Object> ptr;
shared_ptr<Object> ptr(new Object);
shared_ptr<Object> ptr(new Object, [=](Object *){
//回收资源时调用的函数 });
第一种空构造,没有指定shared_ptr管理的堆上对象的指针,所以引用计数为0,后期可以通过reset()成员函数来指定其管理的堆上对象的指针,reset()之后引用计数设为1。
第二种是比较常见的构造方式,构造函数里面可以放堆上对象的指针,也可以放其他的智能指针(如weak_ptr),具体使用参照文档。
第三种构造方式指定了shared_ptr在析构自己所保存的堆上对象的指针时(即引用计数为0时)所要调用的函数,这说明我们可以自定义特定对象的特定析构方式。同样的,reset()成员函数也可以指定析构时调用的指定函数。
除了以上三种构造方式外,我们还有一种比较常见的构造shared_ptr的方式:
auto ptr = make_shared<Object>(args);
这是最安全的一种方式,使用标准库里边的make_shared<>()模板函数。该函数会调用模板类的构造方法,实例化一个堆上对象,然后将保存了该对象指针的shared_ptr返回。参数是该类构造函数的参数,所以使用make_shared<>()就好像单纯地在构造该类对象一样。auto是C++11的一个关键字,可以在编译期间自动推算变量的类型,在这里就是shared_ptr<Object>类型。
shared_ptr的其他成员函数,在这里就简单地列举一下:
use_count() //返回引用计数的个数
unique() //返回是否是独占所有权(use_count是否为1)
swap() //交换两个shared_ptr对象(即交换所拥有的对象,引用计数也随之交换)
reset() //放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
值得一提的是,shared_ptr是一种强引用,引用陈硕书中对强引用的描述就是:就好像对象上面绑了一根根的铁丝。对象身上的铁丝不全数卸干净,对象就无法得到释放,这个比喻还是很贴切的。因此shared_ptr也会带来一定的麻烦,比如他会意外地延长对象的寿命然后的空悬指针。对这些技术上的陷阱感兴趣的同学可以去读读陈硕的那本书。
还有一个问题就是两个shared_ptr对象相互引用造成的死锁问题,这个是比较典型的问题,具体看一下代码:
class B;
class A
{
public:
shared_ptr<B> pb_;
~A
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。