“智能指针”的成长

  智能指针是什么,他的真面目就是一个类生成的对象,这个类中包含了基本的重载->、*等一些指针形态的用法,最主要的是这个类里面有一个指针参数:所有智能指针类中都有一个explicit构造函数,以指针作为参数。比如auto_ptr的类模板原型为:

template<class T>

class auto_ptr{

  explicit auto_ptr(T* p = 0);

};

 

代码中构造函数用了explicit关键字主要是防止隐式转换,举个例子:

auto_ptr<double> pd;

double *p_reg = new double;

pd = p_reg;       //error

pd = shared_ptr<double>(p_reg);    //error

shared_ptr<double> pshared = p_reg;     //error

pd = auto_ptr<double>(p_reg);

shared_ptr<double> pshared(p_reg);

 

上面解释了一下智能指针的本质是什么,那它有什么作用呢,看代码看不出来,但仔细想,作为一个类对象,创建时需要构造,那待生命周期结束,也是需要自动析构的,所以就带出了他的功能,就是为了保证其指向的New出来的对象,可以在生命周期结束时自动Delete,作为程序员我们都知道,Delete这个东西我们经常忘写不说,也在很多return的地方忽略了写Delete,这就导致了内存泄漏,很致命。

所以C++就提供了一种智能指针的模式去防止这种现象,现有最常用的智能指针,分别有 std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_arrayboost::weak_ptr这6个智能指针都有各自的使用范畴,话不多说,研究下就知道。

 

⑴ std::auto_ptr

  首先auto_ptr这个智能指针,属于STL,同属于STL的还有unique_ptr、shared_ptr等,感兴趣的可以去查查,但本文就不进行介绍了。先来看一段代码

 

class Simple {

 public:

  Simple(int param = 0) {

     number = param;

     std::cout << "Simple: " << number << std::endl; 

  }

  ~Simple() {

     std::cout << "~Simple: " << number << std::endl;

  }

  void PrintSomething() {

    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;

  }

  std::string info_extend;

  int number;

};

void fun()

{

  std::auto_ptr<Simple> ptr (new Simple(1));

  if(ptr.get())

  {

    ptr->PrintSomething();

    ptr.get()->info_extend = "Hello";

    (*prt)->info_extend = "World";

  }

}

上一段代码运行的结果为:

  Simple:1

  PrintSometing:

  ~Simple:1

这是最标准的一种智能指针的使用方法,对于auto_ptr这种智能指针也是完全能驾驭的,但假如我们在代码中加入另外几句

std::auto_ptr<Simple> ptr2;

ptr2 = ptr;

pt2->PrintSomething();

pt1->PrintSomething(); // 崩溃

如果将上面的代码加到程序中就会崩溃,为什么呢?看源码中,重载=时,进行了

reset(_Right.release());

继续向里执行release的代码

 

可以看到,这里是将原本的指针控制权交给了别人,自身已经被置为NULL了。所以我们进行调用,不崩溃等什么呢?

然后加另一端代码:

if (ptr.get()) {

  ptr.release();

}

我们可以直接调用release函数,这就很尴尬了,直接返回值为当前指向的控制权,但没有指针接收,直接丢弃了,这就更尴尬了,导致只进行了构造,没有析构。。所以真要使用release函数的话,必须这样

Simple* ptr2 = ptr.release();

delete ptr2;

这恶心的用法总给人一种多此一举的做法,所以我release函数的直接使用是没有任何意义的。对于这种手动的释放内存,直接调用

ptr.reset();

 

所以我们对auto_ptr进行一个总结:

  1,尽量不要使用"="对智能指针进行赋值,如果使用,那先前对象就时一个空指针的。

  2,release函数单独使用没有任何意义,不会释放对象,还有可能导致对象丢失。他的作用仅仅是返回所有权。

  3,不要把智能指针对象当做参数传递进入函数,因为也会调用release,导致调用完函数,回来对象已经被析构。

  上述3个仅仅是个人发现的,对于auto_ptr本身就有很多不合理的地方,虽然还有人使用,但具体项目中用的已经不多了,所以就在auto_ptr的基础上,增加了各种其他的智能指针,用以修复这个使用不合理的地方。

 

⑵ boost::scoped_ptr

  scoped_ptr属于boost库,他与auto_ptr相比,也是避免了上述几个问题,主要的原因就是scoped的设计为独享所有权。主要的体现就是调用release函数和‘=’的时候是会报错的。虽然表面上的确是避免了auto_ptr中出现的问题,但种感觉是一种逃避的方法,那就有了下面的另一种智能指针。

 

⑶ boost::shared_ptr

  scoped_ptr属于boost库,与上述的scoped_ptr相比,它添加了共享所有权的功能,也就是可以使用‘=’方法,关于做法,原理就是在类中使用了一个引用计数,当进行赋值的时候对该数字加1,写一段代码看看:

void TestSharedPtr(boost::shared_ptr<Simple> ptr)

{

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

}

boost::shared_ptr<Simple> ptr(new Simple(1));

std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

{

  boost::shared_ptr<Simple> ptr2 = ptr;

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

  TestSharedPtr(ptr);

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

}

std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

这一段代码运行下来的打印结果是:

TestSharedPtr2 UseCount: 1

TestSharedPtr2 UseCount: 2

TestSharedPtr2 UseCount: 3

TestSharedPtr2 UseCount: 2

TestSharedPtr2 UseCount: 1

大概解释一下,在开始只有一个ptr时,打印出来的引用计数为1,当另外使用一个ptr2时,再次打印出来为2,意思就是当前共有两个智能指针同时使用当前对象。进入函数后,又进行了一次拷贝构造到临时变量,所以打印值又变成3,后续退出函数和退出ptr2的生命周期,引用计数的值各剪了1,当代码执行完毕后,引用u计数值归0,调用析构函数时使用Delete释放空间。

 

⑷ boost::scoped_array

以数组的方式管理多个对象,但属性与scoped_ptr完全一致,独享所有权,而其使用方法也与数组没有太大的区别:

boost::scoped_array<Simple> ptr_array(new Simple[2]);

ptr_array[0].PrintSomething();

 

⑸ boost::shared_array

以数组的方式管理多个对象,属性与shared_ptr王权一致,共享所有权,内部使用引用计数

 

⑹ boost::weak_ptr

weak_ptr是一中比较特殊的智能指针,其应用的场景比较单一,主要是对shared_ptr进行观察的一种对象,也就是说用weak_ptr类型的对象对shared_ptr类型的对象进行引用,是可以正常访问,但不会改变后者的引用计数值,当后者被销毁后,前者也就不能使用了。现在说说它的应用场景,如果子类中有一个shared_ptr类型的对象,那基类中就可以定义一个weak_ptr类型的对象,通过访问基类的weak_ptr对象是否为空,就可以判断子类是否对自己进行赋值。

网上看了一些博客,发现weak_ptr还有一个很常用的场景,如果shared_ptr产生循环引用问题,就需要使用弱指针week与强引用指针相结合使用,什么是循环引用问题呢?看段代码:

{

  shared_ptr<int> tr1(new Node());

  shared_ptr<int> tr2(new Node());

  tr1 -> next = tr2;

  tr2 -> pre = tr1;

}

这段代码如果运行到}处,导致tr1,tr2生命周期结束,需要自己调用析构函数时,就会发生崩溃,原因是什么呢?主要就是在调用析构,发现当前引用计数为1,那就先去让别人析构,但tr2的引用计数同样为2,两个互相让对方先析构,就陷入了一个的僵局,导致最后均不释放,内存泄漏。对于这种情况解决的办法可以有好几种:

⑴ 做一个监控,当最后发现两个智能指针陷入循环阶段,手动打破僵局(定制一个定时器就可以解决)

⑵ 模仿子类或父类的析构方式,如果陷入僵局,则先析构后构造的对象,比如先析构tr2,再析构tr1

⑶ 最简单也是最常用的,使用弱引用型智能指针week_ptr,不会改变引用计数的值,当需要访问的对象已经被析构,可以通过lock()函数返回一个空的shared_ptr,所以就可以自动处理了。所以只需要将上面的代码tr2类型改为weel_ptr即可解决循环引用的问题。

 

本文有部分函数名和编码方式是采用网上其他博主的风格(因为觉得这种讲解方法特别清晰,先简单介绍,然后上代码直接理解),所以有些抄袭的嫌疑,但全部均本人理解,并重新编码之后写上去的,很多地方也是本人通过理解源码,写出来的部分东西,如果有误,请及时沟通修正。

posted @ 2017-07-08 19:58  不想写代码的DBA  阅读(204)  评论(1编辑  收藏  举报