智能指针总结及应用(一)

背景介绍:由于内存问题带来的苦恼:
1、内存泄漏(memory leak)
2、野指针(wild pointer)
3、访问越界(access violation)
boost的智能指针库就是内存管理的强劲解决方案(C++11中的智能指针起源是boost这里就都写boost了)通过boost智能指针库,我们能够高效的进行内存管理,解决上述问题,同时让你忘记栈(stack),堆(heap)等内存相关的术语。

为了管理内存等资源,智能指针采用RAII机制(Resource Acquisition Is Initialization,资源获取既初始化)
1、 所有初始化操作移到对象的构造函数中
2、 所有的释放操作都放在对象的析构函数里。
3、 适当的异常处理代码来应付对象构造期间丢出的异常
这种用构造函数申请资源用析构函数释放资源的做法或者说技术通常被称为“资源获取即初始化”。这样做的优点是:对象创建后,用户能开始正确使用对象,不用担心对象的有效性或者是否还要作进一步的初始化操作。

智能指针的权限分类:
这里写图片描述

Shared_ptr的特点:
1、boost::shared_ptr 是一个最像指针的“智能指针”。
2、boost::shared_ptr实现了计数引用:
它包装了new操作符在堆上分配的动态对象,但它实现了引用计数,可以自由的拷贝和赋值,在任意地方共享它。当引用计数为0时,它会自动删除被包装的动态分配的对象。
3、boost::shared_ptr不需要手动的调用类似release方法:
boost::intrusive_ptr都是基于侵入式设计的智能指针,需要手动调用类似release方法。它不像侵入式实现的智能指针一样需要手动的调用类似release方法,全部用由shared_ptr内部的计数器自动增减,这一点是非常有用的。
4、boost::shared_ptr支持所有权转移:
并且可以安全的存储在stl标准容器中,是在stl容器存储指针的标准解法。
例如

std::vector<int*> IntVec,(这样写是不安全的,再Effictive stl中有讲到)使用shared_ptr方式为std::vector<boost::shared_ptr<int> > IntptrVec.

(插曲:解释一下为什么以为当指针被删除的时候,指针容器在自己被析构时会析构所包含的每个元素,但是指针的析构函数不做任何事,不会调用delete将所指向的元素析构,造成资源泄漏,或者有异常的时候也不能讲资源析构掉,总结起来有两条:删除容器中的指针,并不能删除该指针所指向的对象。 当对包含指针的容器使用remove类的 算法的时候要注意(要么shared_ptr 要么手工删除)否则资源泄漏。)详见Effictive stl 7条和 33条;

shared_ptr类摘要
1、 构造函数,拷贝构造函数,赋值操作符以及析构函数

Template<class T> Class shared_ptr
{
      /*******************构造函数*******************************************/
      shared_ptr();  //创建一个持有空指针的shared_ptr,use_count() = 0&& get() == NULL
      //获得一个指向类型T的指针p的管理权,use_count == 1&&get() == p ,Y类型必须能够转换为T类型  
      template<class Y> explicit shared_ptr(Y* p);  
      //作用同上,增加了一个构造参数D,是一个仿函数对象,代表删除器,该构造函数非常有用,后面会详解
      template<class Y, class D> shared_ptr(Y* p, D d);
      /******************拷贝构造函数及赋值操作符重载***********************/
      //调用拷贝构造函数或赋值操作后,引用计数加1 && get() == r.get() && use_count() == r.use_count()
      shared_ptr(shared_ptr const & r); 
      template<class Y> shared_ptr(shared_ptr<Y> const & r); 
       shared_ptr & operator=(shared_ptr const & r); 
       template<class Y> shared_ptr & operator=(shared_ptr<Y> const & r); 
       /******************析构函数*******************************************/
      //引用计数会减1,如果计数器为0,get() != NULL,有删除器的话会调用删除器,否则调用delete操作符
       ~shared_ptr();   
}   

2、public 成员方法和操作符

Template<class T> class shared_ptr
{
      T*    get() const ;                   //返回原始指针
      T&   operator * () const ;           //返回原始指针的引用
      T*    operator ->() cosnt ;         //返回原始指针

      long use_count() const ;          //返回引用计数
      bool unique() const ;            // 返回 use_count() == 1,是否唯一

      operator bool() const ;   //bool值转型,可以用于条件判断,例如if(ptr){…}
      void swap(shared_ptr& b)       //交换原始指针

      void reset() ; 
      template<class Y> void reset(Y* p);
      template<class Y, class D> void reset(Y* p);
} 

1、#include “boost/shared_ptr.hpp”

2、创建一个shared_ptr要用到的简单类Foo

class Foo
{
private:
     std::string m_strName;
Public:
     Foo(const std::string& strName) : m_strName(strName){}
     ~Foo() {std::cout << “Destructing Foo with name = “ << m_strName << std::endl}
};
typedef boost::share_ptr<Foo> FooPtr;
void Test_Boost_Shared_Ptr()
{            
     //ptr1获得Foo__1指针的所有权
    FooPtr ptr1(new Foo(“Foo1”));//引用计数为1
    assert(ptr1.use_count() == 1);

     //ptr2指向ptr1
    FooPtr ptr2 = ptr1;       //调用shared_ptr的赋值操作符,引用计数加1
    assert(ptr1.use_count() == ptr2.use_count());  //两者引用计数相同
    assert(ptr1 == ptr2);    //shared_ptr重载==操作符,等同于     ptr1.get() == ptr2.get()。
    assert(ptr1.use_count() == 2); //现在ptr1和ptr2都指向了Foo__1,因此计数器都为2
//ptr3获得Foo__1指针的所有权
             FooPtr ptr3 = ptr2;//引用计数加1
    assert(ptr1.use_count() == ptr2.use_count() && ptr1.use_count()== ptr3.use_count()); 
    assert(ptr1.use_count() == 3 && ptr2.use_count() == 3 && ptr3.use_count() == 3);
    assert(ptr1 == ptr2 && ptr1 == ptr3);
     //现在我们重置ptr3,测试reset()函数
    ptr3.reset();
    assert(ptr3.use_count() == 0 && ptr3.get() == NULL);
    std::cout << "ptr3引用计数为0,get() 指针指向NULL,但是不会调用析构函数,因为ptr1和ptr2都指向了原生指针" << std::endl;
    assert(ptr1.use_count() == ptr2.use_count() && ptr1.use_count() == 2);
    assert(ptr1 == ptr2 && ptr1 != ptr3);
    //前面ptr1和ptr2都指向了同一个对象,他们的引用计数都为2,现在创建一个ptr4,让其指向ptr1。
    FooPtr ptr4 = ptr1; //引用计数加1
    assert(ptr1 == ptr2 && ptr4 == ptr1);
    assert(ptr4.use_count == 3 && ptr1.use_count == 3 && ptr2.use_count() == 3);
    //现在我们转移ptr2的所有权到另外一个新分配的名为Foo__2的Foo指针上去
   //在ptr2转移所有权后,ptr2的计数器应该是1 && ptr1和ptr4的计数器为2 && ptr1 != ptr2
    ptr2.reset(new Foo(“Foo2”);
    assert(ptr2.use_count() == 1 && ptr1.use_count() == 2 && ptr4.use_count() == 2);
   assert(ptr1 != ptr2 && ptr1 == ptr4);
   //前面ptr3因为被reset()为0,并且get() = NULL了,我们再用ptr5来指向ptr3,此时ptr5的引用计数也应该
   //为0而不是加1,并且ptr5.get()返回值应该也为NULL
   FooPtr ptr5 = ptr3; //引用计数为0
   assert(ptr5.use_count() == 0 && ptr5.get() == NULL);
   //运行到此时,程序即将结束,在退出作用域前会调用析构函数
   //首先会调用ptr2的析构函数,打印出Destructing a Foo with name = Foo__2
   //然后会调用ptr1 和ptr4析构函数进行引用计数递减
   //最终会打印出Destructing a Foo with name = Foo__1
   //这样就没有任何内存泄露了哈哈哈哈
}//结束test_boost_shared_ptr函数

shared_ptr使用注意点

1、shared_ptr多次引用同一内存数据导致程序崩溃

例如下面代码会导致堆内存破坏而引起程序奔溃
   void test_boost_shared_ptr_crash()
   {
        Foo* pFoo = new Foo("Foo1");
        boost::shared_ptr<Foo> ptr1(pFoo);
        boost::shared_ptr<Foo> ptr2(pFoo);
   }   
  上述代码两次释放同一内存而破坏堆,导致程序奔溃。

解决方案:
1 )直接使用 boost::shared_ptr ptr1(new int(100));
使用匿名内存分配限制其他shared_ptr多次指向同一内存数据。
多次指向时使用shared_ptr的赋值操作符或拷贝构造函数,例如shared_ptr ptr2 = ptr1。
上面的匿名方式就是资源初始化既分配技术,直接在构造函数中分配内存
2) 使用boost的make_shared模板函数,例如shared_ptr ptr1 = boost::make_shared(100); shared_Ptr ptr2 = ptr1;

2、shared_ptr 循环引用导致内存泄露,代码如下:

  class Parent;
  class Child;
  class Parent
    {
     public:
     ~Parent(){ std::cout << “父类析构函数被调用\n”;}
     public:
     boost::shared_ptr<Child> child_ptr;
    };
    class Child
    {
     public:
     ~Child(){ std::cout << “子类析构函数被调用\n”;}
     public:
     boost::shared_ptr<Parent> Parent_ptr;
    };
   int main()
    {
        boost::shared_ptr<Parent> father(new Parent());//father引用计数为1
       boost::shared_ptr<Child>   son(new Child());//son引用计数为1
       // 父子互相引用。
       father->children = son;//son的引用计数为2
       son->parent = father;//father的引用计数为2
       return 0;
       //退出作用域前,father和son的引用计数都减1,此时father和son的引用计数都是为1
      //因此各自指向的内存无法释放,导致内存泄露
    }

针对循环引用,使用boost::weak_ptr可以很方便的解决该问题。代码如下:

    class Parent;
    class Child;
    class Parent
    {
     public:
     ~Parent(){ std::cout << “父类析构函数被调用\n”;}
     public:
    // boost::shared_ptr<Child> child_ptr;改为如下代码:
     boost::weak_ptr<Child> child_ptr;
    };

对weaked_ptr的介绍:
1 、weak_ptr是用来解决循环引用和自引用对象
从前面的例子我们可以看出,引用计数是一种很便利的内存管理机制,但是有一个很大的缺点,那就是不能管理循环引用或自引用对象(例如链表或树节点),为了解决这个限制,因此weak_ptr被引入到boost的智能指针库中。

2、weak_ptr并不能单独存在
它是与shared_ptr同时使用的,它更像是shared_ptr的助手而不是智能指针,因为它不具备普通指针的行为,没有重载operator *和->操作符,这是特意的。这样它就不能共享指针,不能操作资源,这正是它“弱”的原因。它最大的作用是协助shared_ptr工作,像旁观者那样观察资源的使用情况。
3、 weak_ptr获得资源的观察权
weak_ptr可以从一个shared_ptr或另外一个weak_ptr构造,从而获得资源的观察权,但weak_ptr并没有共享资源,它的构造并不会引起引用计数的增加,同时它的析构也不会引起引用计数的减少,它仅仅是观察者。

4、 weak_ptr可以被用于标准容器库中的元素
weak_ptr实现了拷贝构造函数和重载了赋值操作符,因此weak_ptr可以被用于标准容器库中的元素,例如:在一个树节点中声明子树节点std::vector<boost::weak_ptr<Node> >children;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值