不少编程语言都有GC的机制,能够自动管理内存资源,而后GC机制带来的是资源释放的不肯定性,c++原始的手工管理内存资源的方式虽然具备释放的肯定性,可是人工管理很是容易出错;如何既能自动释放内存又能保证肯定性呢,modern c++给出的方案是shared_ptr。c++
从c++11开始引入的shared_ptr,用来表示指针对指向对象的“共享全部权”;一个对象能够被多个shared_ptr指向和访问,这些shared_ptr类型的指针共同享有该对象的全部权,当最后一个指向该对象的shared_ptr生命周期结束的时候,对象被销毁。编程
shared_ptr基于引用计数实现,shared_ptr的构造将引用计数加1,销毁的时候引用计数减1,而赋值则将源指针引用计数加1,目标指针引用计数减1,例如P1=P2,P1指向对象的引用计数减1,P2指向对象的引用计数加1。当引用计数减1以后为0的时候,shared_ptr将会销毁指向的对象。数组
存储引用计数的空间是动态分配的。此外,为了线程安全,引用计数的加减都必须是原子操做,原子操做的实现带来了性能上的损耗;上面提到,shared_ptr的构造函数函数会增长引用计数,可是移动构造除外,由于移动构造并无增长指向对象的引用计数,因此不须要改变引用计数;安全
与unique_ptr相似,shared_ptr一样也支持自定义销毁方法(默认是直接调用delete),与unique_ptr不一样的是,销毁方式是unique_ptr类型的一部分,而shared_ptr的销毁方式却不是。编程语言
auto loggingDel = [](Widget *pw) { makeLogEntry(pw); delete pw; }; std::unique_ptr<Widget, decltype(loggingDel)> upw(new Widget, loggingDel); std::shared_ptr<Widget> spw(new Widget, loggingDel);
不把销毁方式做为shared_ptr类型的一部分能够带来更大的灵活性,由于这里不一样的shared_ptr<Widget>指针对象可能须要不一样的销毁方式函数
auto customDeleter1 = [](Widget *pw) { … }; // custom deleters, auto customDeleter2 = [](Widget *pw) { … }; // each with adifferent type std::shared_ptr<Widget> pw1(new Widget, customDeleter1); std::shared_ptr<Widget> pw2(new Widget, customDeleter2);
另外一个与unique_ptr不一样的是,自定义销毁方式并不会改变shared_ptr的size,shared_ptr的size始终是两倍的裸指针size,其内存布局是以下图所示:布局
由图中能够看到,实际上引用计数、自定义销毁等都不是直接存储在shared_ptr中,而是经过一个指针指向的一个控制块存储的,控制块是动态分配的内存,对shared_ptr进行不一样的操做时,须要判断须要不要分配新的控制块,控制块的分配主要有如下几种状况:性能
使用std::make_shared的时候老是分配控制块this
shared_ptr由unique_ptr或裸指针构建时分配控制块spa
shared_ptr由其余shared_ptr或weak_ptr构建时不分配新的控制块,而是沿用既有智能指针的控制块
由上面的2引出的一个问题,当咱们用一个裸指针构建多个shared_ptr时,会分配多个控制块,这就致使一个问题,同一个对象确有多个引用计数(控制块),这就很容易致使一个对象被销毁屡次,下面的代码描述了这种状况:
auto pw = new Widget; // pw is raw ptr //… std::shared_ptr<Widget> spw1(pw, loggingDel); // create control block for *pw std::shared_ptr<Widget> spw2(pw, loggingDel); // create 2nd control block
有两个方法能够避免上面的问题发生,
1 尽量避免使用裸指针来构建shared_ptr,使用make_shared
2 必须使用裸指针的话,new出对象后直接传入,而不是将指针传入
std::shared_ptr<Widget> spw1(new Widget, loggingDel);
另一种状况是涉及到this指针的问题,假如在某个类的实现代码中,用this指针构建了一个shared_ptr的时候:
class Widget { public: … void process(){ … processedWidgets.emplace_back(this); //add it to the list of processed widgets } … private: std::vector<std::shared_ptr<Widget>> processedWidgets; };
上面这段代码是能够正常编译的,这里使用this指针构建了一个shared_ptr并保存到了一个vector中,这就给当前对象生成了一个控制块,可是实际上,因为外部使用shared_ptr管理这个对象,该对象已经存在一个控制块了;
std::shared_ptr<Widget> spw1(new Widget);//产生一个控制块
为了解决这个问题,标准库引入了一个新的模板类enable_shared_from_this,自定义的类能够经过继承这个模板类来规避由this指针构建shared_ptr的问题,上面的代码修改以后以下面所示:
class Widget: public std::enable_shared_from_this<Widget> { public: … void process() { // as before, process the Widget … // add std::shared_ptr to current object to processedWidgets processedWidgets.emplace_back(shared_from_this()); } … };
经过调用enable_shared_from_this类的shared_from_this函数来获取对象自己的shared_ptr指针,就不会再建立一个新的控制块了。shared_from_this会自动去查找关联了当前对象的控制块,并建立一个shared_ptr指针引用已有的控制块,实际的状况中,对象函数被调用必然是在对象已经存在的前提下,因此当前对象关联的控制块老是存在的,若是shared_from_this未查找到当前对象关联的控制块,就会致使未定义行为,一般会抛出一个异常。
为了不上面的状况,能够把类的构造函数设为私有,再经过一个工厂方法函数返回shared_ptr来确保客户端在调用的时候对象必定关联了控制块
class Widget: public std::enable_shared_from_this<Widget> { public: // factory function that perfect-forwards args // to a private ctor template<typename... Ts> static std::shared_ptr<Widget> create(Ts&&... params); … void process(); // as before … private: … // ctors };
关于shared_ptr性能的讨论
shared_ptr的控制块是动态生成的,尽管占用的空间并不大,可是控制块的实际实现比想象的要复杂,实现控制块使用到了继承和虚函数,同时引用计数的增减是原子操做也增长了性能上的代价,这些都致使了shared_ptr并非管理全部动态资源的最好方案,使用shared_ptr解引用获取对象时会比直接使用裸指针的代价更高;
然而,尽管shared_ptr有在性能上付出了必定的代价,其带来的收益是很是显著的,shared_ptr解决了动态分配资源的生命周期自动管理,大多数时候,在“共享全部权”的语义下,使用shared_ptr管理动态资源都是值得推荐的;而没有“共享全部权”语义的其余状况下,例如“独占全部权”,则可使用unique_ptr来代替;
另外一个shared_ptr不能作的事情是管理数组,不能吃std::shared_ptr<T[]>这样的类型,然而,c++ 11以后标准库已经引入了std::array,shared_ptr管理一个std::array类型的对象是可行的。
shared_ptr的注意点
shared_ptr能够用来管理具备“共享全部权”语义的动态资源,能够自动管理对象的生命周期和GC
因为control_block的存在,shared_ptr的size一般是2倍裸指针或unique_ptr的大小,此外,shared_ptr的引用计数增减是原子操做
shared_ptr一样支持自定义销毁方式,且自定义销毁方式与shared_ptr类型无关
避免使用裸指针构建shared_ptr,在有经过this指针构建shared_ptr的状况下要继承std::enable_shared_from_this