std::智能指针

 

auto_ptrc++

用于解决资源自动释放问题,见以下代码:
void Function()
{
 Obj*p = new Obj(20);
 ...
 If (error occor)
  throw ... 或者 retrun;
 delete p;
}
在函数遇到错误以后,通常会抛异常,或者返回,可是这时可能泄露以前申请的资源,使用auto_ptr会在析够函数中进行资源释放。使用auto_ptr代码以下
void Function(){
  auto_ptr<Obj> ptr(new Obj(20));
  ...程序员

  if (error occur)
   throw exception...
}
这样不管函数是否发生异常,在何处返回,资源都会被自动释放。
注:auto_ptr是被c++11标准废弃的一个智能指针,为何会被废弃,见如下面代码:
auto_ptr<Obj> ptr1( new Obj() );
ptr1->FuncA();
auto_ptr<Obj> ptr2 = ptr1;
ptr2->FuncA();
ptr1->FuncA();  // 这句话会异常
为何在将ptr1复制给ptr2以后ptr1使用致使异常?这正是它被抛弃的主因。由于auto_ptr复制构造函数中把真是引用内存指针进行了转移,就是从ptr1转移给ptr2,此后,ptr2引用了Obj内存地址,而ptr1引用内存地址为空,此时再使用ptr1则异常。安全

unique_ptr函数

unique_ptr做为auto_ptr替代品。它对引用对象全部权专注,所以得名unique
1) 没法进行复制构造和赋值操做
auto_ptr与unique_ptr的对比:
auto_ptr<Obj> ap(new Obj() );
auto_ptr<Obj> one (ap) ; // ok
auto_ptr<Obj> two = one; //ok

spa

unique_ptr<Obj> ap(new Obj() );
unique_ptr<Obj> one (ap) ; //error指针

unique_ptr<Obj> two = one; //errorc++11

也就是unique_ptr对对象的引用专注,不容许随意转移。对象

2)  可进行移动构造和移动赋值操做
unique_ptr<Obj> GetObj()
{
  unique<Obj> ptr( new Obj() );
  return ptr;
}
unique<Obj> ptr = GetObj();
上面的代码可顺利执行!但万一须要将一个unique_ptr智能指针赋值给另一个怎么办呢?可以使用移动函数:什么是移动构造和移动赋值呢?(见C++11的标准规范)。
unique<Obj> ptr1(new Obj());
unique<Obj> ptr2(std::move(ptr1));
该效果与auto_ptr直接赋值同样,ptr1再也不拥有Obj对象,以后ptr1不能再操做内存中Obj对象。 但由于移动操做为程序猿主动操做,所以程序员会留意使用。递归

shared_ptr内存

auto_ptr和unique_ptr只能用一个智能指针引用对象,而shared_ptr则是能够多个智能指针同时拥有一个对象。shared_ptr实现方式是采用引用计数。引用计数原理即多个智能指针同时引用一个对象,每当引用一次,引用计数加1,当一个智能指针销毁,引用计数减1,当引用计数减小到0的时释放引用对象。这种引用计数增减发生在智能指针的构造函数,复制构造函数,赋值操做符,析构函数中。这种方式使得多个智能指针同时对所引用的对象有拥有权,同时在引用计数减到0以后自动释放资源,也实现了auto_ptr和unique_ptr的资源释放功能。
void Function()
 {
  shared_ptr<Obj> ptr1(new Obj() ); // 引用计数为1
  {
   shared_ptr<Obj> ptr2(ptr1); // 引用计数为2
   {
     shared_ptr<Obj> ptr3 = ptr2; // 引用计数为3
     int e = 0;

   }
   //引用计数为2
  }
  //引用计数为1
}
//函数返回以后引用计数为0,new 出来的Obj内存被释放。

因为shared_ptr支持复制构造,因此它可做为标准库容器中的元素
vector<shared_ptr<Obj>> vect;
for (int i = 0; i < 10; ++i){
  vect.push_back( shared_ptr<Obj>( new Obj() ) );
}
vector<shared_ptr<Obj>> vect2 = vect;
这些操做是auto_ptr和unique_ptr不能实现的。注意,智能指针默认使用delete来释放资源,若是资源是FILE*怎么办?释放的时候就须要用fclose了。如何实现呢? 
shared_ptr构造函数能够传递一个删除器。
FILE* pStm = fopen(...);
shared_ptr<FILE> fileRes(pStm, &fclose);
或者使用一个仿函数
class FileCloser { 
     public: 
        void operator()(FILE* file) { 
         std::cout << "The FileCloser has been called with a FILE*, " 
           "which will now be closed.\n"; 
         if (file!=0)  
           fclose(file); 
       } 
     }; 
shared_ptr<FILE> fileRes(pStm, FileCloser);

weak_ptr

shared_ptr是一种强引用的关系,智能指针直接引用对象。那么这个代码有一个隐含的问题,就是循环引用,从而形成内存泄漏。示例看一个循环引用的例子。
class Parent{
public:
    shared_ptr<Child> child;
};
class Child{
public:
    shared_ptr<Parent> parent;
};
void Function() {
  shared_ptr<Parent> pA(new Parent);
  shared_ptr<Child> pB(new Child);
  pA->child = pB;
  pB->parent = pA;
}
如今来分析一下Function函数的执行过程:
1. 第一条语句使得pA引用了Parent一个指针,Parent引用计数为1
2. 第二条语句使得pB引用了Child一个指针,Child引用计数为1
3. 第三条语句,调用了shared_ptr<Child>类赋值操做符,使得Child引用计数变为2
4. 第四条语句,调用了shared_ptr<Parent>类赋值操做符,使得Parent引用计数变为2
5. 函数返回以前调用了shared_ptr<Parent>和shared_ptr<Child>类的析够函数,使得Child引用计数变为1,Parent引用计数变为1
看!函数执行完以后new出来的Parent和Child并无释放,因此出现了内存泄漏。
出现泄漏的缘由就是pA和pB相互引用了,致使二者所引用对象的引用计数不能减小到0,形成泄漏。
若是把第三条语句或者第四条语句任意删除一个,则不会泄漏了。这就是强引用所致问题。
weak_ptr字面意思即一个弱指针,并不指该指针能力弱,而是指对它所引用对象全部权弱,它并不拥有所引用对象全部权,且不能直接使用所引用对象。在stl中,weak_ptr是和shared_ptr配合使用的,在实现shared_ptr的时候也就考虑了weak_ptr的因素。
weak_ptr是shared_ptr的观察者,它不会干扰shared_ptr所共享对象的全部权,
当一个weak_ptr所观察的shared_ptr要释放它的资源时,它会把相关的weak_ptr的指针设置为空,防止weak_ptr持有悬空的指针。
注意:weak_ptr并不拥有资源的全部权,因此不能直接使用资源。
能够从一个weak_ptr构造一个shared_ptr以取得共享资源的全部权。
void Function()
{
 shared_ptr<int> sp( new Obj() );
 assert(sp.use_count() == 1);
 weak_ptr<int> wp(sp); //从shared_ptr建立weak_ptr
 assert(wp.use_count() == 1);
 if (!wp.expired())//判断weak_ptr观察的对象是否失效
 {
  shared_ptr<int> sp2 = wp.lock();//得到一个shared_ptr
  *sp2 = 100;
  assert(wp.use_count() == 2);
 }
 assert(wp.use_count() == 1);
 return 0;
}
weak_ptr并无重载-> 和 * 操做符,因此咱们不能经过他来直接使用资源,咱们能够经过lock来得到一个shared_ptr对象来对资源进行使用,若是引用的资源已经释放,lock()函数将返回一个存储空指针的shared_ptr。 expired函数用来判断资源是否失效。使用weak_ptr并不会增长资源的引用计数。因此对资源的引用是弱引用,利用这个特性能够解决前面所说的循环依赖问题。

class Parent {
public:
    weak_ptr<Child> child;
};
class Child {
public:
    weak_ptr<Parent> parent;
};
void Function()
{
 shared_ptr<Parent> pA(new Parent);
 shared_ptr<Child> pB(new Child);
 pA->child = pB;
 pB->parent = pA;
}
此时第三和第四条语句的执行并无增长引用计数,从而在函数执行完成只有能自动释放内存。以上分析可见,weak_ptr是一种辅助shared_ptr的智能指针,通常不单独使用,而是结合shared_ptr一块儿使用。

应用总结

尽可能使用unique_ptr而不是auto_ptr

shared_ptr知足大部分需求;

weak_ptr避免递归的依赖关系;

 

make_shared

1. 在初始化unique_ptr或者shared_ptr时,优先使用std::make_unique和std::make_shared。缘由:

1)异常安全性

假设有以下函数声明:

intcomputePriority();
void processInvestment(std::shared_ptr<Investment> ptr,int priority);
调用processInvestment的代码以下所示:

processInvestment(std::shared_ptr<Investment>(newInvestment()),computePriority());

因为在C++中函数参数的执行顺序不固定,因此在上面对函数processInvestment调用中,函数参数的执行顺序极可能是:

 new Investment

 computePriority()

 std::shared_ptr constructor

这种执行顺序的风险是,若是在第二步,执行computePriority的过程当中出现异常,那么在第一步中new出来的对象将变得不可访问,从而形成内存泄漏。
经过make_ptr的方式,将new操做和shared_ptr的构造放在一块儿执行,就不会出现内存泄漏问题了。

2)执行效率(对于shared_ptr而言)

std::shared_ptr<Investment>ptr(new Investment); //方式1,new的方式在方式1中,会涉及到两次动态内存分配:

第1次是new Investment时,为Investment对象分配空间; 第2次是为控制块(Control Block)分配空间。

auto pIn = std::make_shared<Investment>();//方式2,make_shared的方式

在方式二中,一次动态内存分配就足够了,这是因为make_shared会为Investment对象和控制块(Control block)一次性分配一大块内存。因为只有一次内存分配,于是方式二提升了程序的执行效率。

二、make_xxx函数的弊端

既然使用make_xxx有这么多好处,是否应到处使用make函数而彻底放弃new方式?固然不是,make函数存在如下限制:

1) make函数不支持用户自定义释放器。因为make函数有本身的内存分配和析构规则,因此不适用于自定义分配器和释放器的对象。

2) make函数不支持大括号初始化方式。对于下面这句代码:

auto spv = std::make_shared<vector<int>>(10,20);

意为spv指向一个vector,该vector有10个元素,每一个元素值为20。若是想实现的是这个vector有两个元素,分别为10,20的话,只能用new方式。

3) 内存释放不够灵活。

在使用new的方式中,有两块独立的堆内存,一块存放资源对象,一块存放控制块,当资源对象的引用计数为0的时候,资源对象会被销毁,它所占用的内存也会被随之销毁。

在使用make函数的方式中,make函数一次性为资源对象和控制块分配了一块内存,当资源对象的引用计数为0是,对象被销毁,可是资源对象占用的内存却不会被销毁,只有当控制块占用的内存被销毁是才会将资源对象所占内存一并释放。那么,控制块内存何时被释放呢?这就涉及到控制块中另外一个引用计数了,这个引用计数被称为“Weak Count”,其做用是用来计数指向该资源的weak_ptr的数量。当这个weak count的值为0时,控制块才会被释放。当资源对象很是庞大时,使用make函数的方式将形成不小的资源浪费。

相关文章
相关标签/搜索