注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Mr.Right

不顾一切的去想,于是我们有了梦想。脚踏实地的去做,于是梦想成了现实。

 
 
 

日志

 
 
关于我

人生一年又一年,只要每年都有所积累,有所成长,都有那么一次自己认为满意的花开时刻就好。即使一时不顺,也要敞开胸怀。生命的荣枯并不是简单的重复,一时的得失不是成败的尺度。花开不是荣耀,而是一个美丽的结束,花谢也不是耻辱,而是一个低调的开始。

网易考拉推荐

阿英讲智能指针的原理以及shared_ptr的应用  

2013-02-20 23:16:16|  分类: 编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

即以此功德,庄严佛净土。上报四重恩,下济三途苦。惟愿见闻者,悉发菩提心。在世富贵全,往生极乐国。

智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。目的: 这样用智能指针 iterIntList 就像在 int a=7; int* p = &a;中用p一样方便,不用考虑p的delete问题, 从而不会造成内存泄露!


每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。


用一个类把指针封装起来。封装好后,这个类对象可以出现在用户类使用指针的任何地方,表现为一个指针的行为。我们可以像指针一样使用它,而不用担心普通成员指针所带来的问题,我们把这样的类叫句柄类。在封装句柄类时,需要申请一个动态分配的引用计数空间,指针与引用计数分开存储。实现示例如下:

 

在heap上建立的对象总是成对出现的, 可惜普通指针没有构造函数与析构函数,所以我们必须要写一个类来加一层包装

int* p = new int; // 1 
delete p; // 2 

“也就是说,对于每一个 1 ,我们要保证有一个 2 被调用,1 和 2 必须成对出现。智能指针的赋值运算符、拷贝构造函数和析构 会保证计数值始终等于指向该内存块的智能指针数。”“然后一旦计数值为 0 被分配的内存块就会释放!也就是说 …… 有指针指向内存块,它就不释放,一旦没有,它就自动释放!太棒了!我们只要一开始正确的初始化智能指针,就可以象普通指针(如int* ptrInt)那样使用它,而且完全不用担心内存释放的问题!太棒了!

对于简单的类型,这个类确实可以处理的很好,但实际情况是很复杂的。考虑一个典型情况:类 Derived 是类 Base 的派生类,我们希望这样赋值:” 

Base* pb; 
Derived pd; 
………… 
pb = pd; 

“你倒说说看,这种情况,怎样改用上面这个智能指针来处理?” 

“要实现一个完整的垃圾收集机制并不容易,因为有许多细节要考虑。”不过,基本思路就是上面说的这些。值得庆幸的是,目前已经有了一个相当成熟的‘引用计数’智能指针,shared_ptr。 大多数情况下,我们都可以使用它。





#include <iostream>

#include <stdexcept>

using namespace std;


#define TEST_SMARTPTR

class Stub

{

public:

    void print() {

        cout<<"Stub: print"<<endl;

    }

    ~Stub(){

        cout<<"Stub: Destructor"<<endl;

    }

};


template <typename T>

class SmartPtr 

{

public:

    SmartPtr(T *p = NULL): ptr(p), pUse(new size_t(1)) { } //妙! 我以前没看出来这样使得 *pUse = 1(初始化计数值为 1 ),

//Remark: 为每个指针维护一个引用计数值,每次赋值或者拷贝构造,就让计数值加一

    SmartPtr(const SmartPtr& src): ptr(src.ptr), pUse(src.pUse) { //拷贝构造函数的参数是固定的: const SmartPtr& src

        ++*pUse;

/*

ptr = src.ptr; // 指向同一块内存 

pUse = src.pUse; // 使用同一个计数值 

++(*pUse); // 计数值加 1 

*/

    }

    SmartPtr& operator= (const SmartPtr& rhs) {

/*

如果我们对一个指针做赋值,它的含义是什么?

在这种情况下,应该想办法让两个智能指针指向同一个对象

*/

        // self-assigning is also right

        ++*rhs.pUse; // rhs.pUse指向的计数值加 1,  即++(*rhs.pUse); 

        decrUse(); // pUse指向的计数值减 1 ,因为该左手边的ptr指针经过右边的rhs.ptr赋值后已经不用了


        ptr = rhs.ptr;  // 指向同一块内存 

        pUse = rhs.pUse; //使用同一个计数值 

        return *this;

    }

// 运算符函数定义的一般格式:  <返回类型说明符> operator <运算符符号>(<参数表>)

// 重载 -> 运算符, 注意返回值类型为 T *

    T * operator->() {

        if (ptr)

            return ptr;

        throw std::runtime_error("access through NULL pointer");

    }

    const T * operator->() const { 

        if (ptr)

            return ptr;

        throw std::runtime_error("access through NULL pointer");

    }


// 重载 * 运算符,注意返回值类型为 T &

    T & operator*() {

        if (ptr)

            return *ptr;

        throw std::runtime_error("dereference of NULL pointer");

    }

    const T & operator*() const {

        if (ptr)

            return *ptr;

        throw std::runtime_error("dereference of NULL pointer");

    } 


    ~SmartPtr() {

        decrUse();

#ifdef TEST_SMARTPTR

        std::cout<<"SmartPtr: Destructor"<<std::endl; // for testing

#endif

    }

    

private:

    void decrUse() {

        if (--*pUse == 0) {  //--*pUse 是计数值先减 1 , 再判断: 当*pUse == 0表示已经没有别的指针指向该内存块了

            delete ptr;

            delete pUse;

        }

    }

    T *ptr;

    size_t *pUse; // size_t 就是健壮的int

};


int main()

{

    try {

        SmartPtr<Stub> t; // t 就是一个Stub类对象的智能指针, 调用默认构造函数SmartPtr(T *p = NULL)

  cout << "first test" << endl;

        t->print(); // t调用重载的 -> 运算符对应的函数,  因为

    } catch (const exception& err) {

        cout<<err.what()<<endl;

    }

    SmartPtr<Stub> t1(new Stub); //调用拷贝构造函数SmartPtr(const SmartPtr& src)

 cout << "second test" << endl << endl;


    SmartPtr<Stub> t2(t1); //调用拷贝构造函数SmartPtr(const SmartPtr& src)

 cout << "third test" << endl<< endl;


    SmartPtr<Stub> t3(new Stub);

 cout << "fourth test" << endl;

    t3 = t2; // 调用重载的赋值运算符  =


    t1->print();

    (*t3).print();

    

    return 0;

}


Demo:Boost中的智能指针自动释放悬垂指针和STL中手动释放悬垂指针

Example: Boost ptr_list:

//The boost ptr_list will perform memory management and "delete" pointers it is containing. 
#include <boost/ptr_container/ptr_list.hpp>
#include <iostream>

using namespace std;

class ABC
{
public:
   int i;
   float j;
};

main()
{
   boost::ptr_list<ABC> intList; // intList 里面装的是ABC 类对象的指针, 注意没有把类对象直接放入容器中的!
   boost::ptr_list<ABC>::iterator iterIntList; 

   ABC *a= new ABC;
   ABC *b= new ABC;
   ABC *c= new ABC;

   a->i = 1;
   b->i = 2;
   c->i = 3;

   intList.push_back(a);
   intList.push_back(b);
   intList.push_back(c);

   for (iterIntList = intList.begin();
        iterIntList != intList.end();
        iterIntList++)
   {
      cout << iterIntList->i << endl;  //  iterIntList 就是个指向类对象的智能指针,不是STL中的(*iterIntList)哦, iterIntList的->是经过重载过的,iterIntList 的本质是一个对象,在iterIntList 

//指向的intList销毁后, iterIntList 里包装的真正指针会自动被delete掉。 


// 目的: 这样用智能指针 iterIntList 就像在 int a=7; int* p = &a;中用p一样方便,不用考虑p的delete问题, 从而不会造成内存泄露!


   }

   intList.clear(); // All pointers held in list are deleted as the object destructed.

}

 

  Example: STL list of pointers:
// g++ -g  testStlPtrList.cpp

//This example shows how one must delete each pointer individually. 
#include <iostream>
#include <list>

using namespace std;

class ABC
{
public:
   int i;
   float j;
};

main()
{
   list<ABC*> intList;  // 这个intList 容器中装的都是ABC类的指针, 而不是ABC类的类对象, 如果装类对象的话就太大了也不是最初的初衷!!!
   list<ABC*>::iterator iterIntList;  // iterator 本身就是一个指针, 现在它是指针的指针

   ABC *a= new ABC;
   ABC *b= new ABC;
   ABC *c= new ABC;

   a->i = 1;
   b->i = 2;
   c->i = 3;

   intList.push_back(a);
   intList.push_back(b);
   intList.push_back(c);

   for (iterIntList = intList.begin();
        iterIntList != intList.end();
        iterIntList++)  // iterIntList是*iterIntList的指针,*iterIntList是类对象的指针
   {
      cout << (*iterIntList)->i << endl; // 这里 *iterIntList 才是ABC类对象的指针, 因为这个容器中装的是类对象的指针,不是类对象本身!
   }

   // Free pointers!!!!!  因为容器里装的是类对象的指针,故要将指针delete。可以想象如果容器不是装的指针,则不用这样做!
   for (iterIntList = intList.begin();
        iterIntList != intList.end();
        iterIntList++)
   {
      delete *iterIntList; // 将指针(*iterIntList)指向堆内存上的对象销毁,   应该知道栈内存上的对象不用销毁
   }

   intList.clear(); // List is deleted.

}


#include <iostream>

#include <list>

#include <numeric>

#include <algorithm>


using namespace std;


//创建一个list容器的实例LISTINT

typedef list<int> LISTINT;


//创建一个list容器的实例LISTCHAR

typedef list<char> LISTCHAR;


int main(void)

{

    //--------------------------

    //用list容器处理整型数据

    //--------------------------

    //用LISTINT创建一个名为listOne的list对象

    LISTINT listOne;

    //声明i为迭代器

    LISTINT::iterator i;  // i 的本质就是一个指针, 容器譬如超市的储物柜,i就是储物柜上的编号


    //从前面向listOne容器中添加数据

    listOne.push_front (2);

    listOne.push_front (1);


    //从后面向listOne容器中添加数据

    listOne.push_back (3);

    listOne.push_back (4);


    //从前向后显示listOne中的数据

    cout<<"listOne.begin()--- listOne.end():"<<endl;

    for (i = listOne.begin(); i != listOne.end(); ++i) // 注意这里的i不用delete

        cout << *i << " ";

    cout << endl;



    //从后向后显示listOne中的数据

LISTINT::reverse_iterator ir;

    cout<<"listOne.rbegin()---listOne.rend():"<<endl;

    for (ir =listOne.rbegin(); ir!=listOne.rend();ir++) {

        cout << *ir << " ";

    }

    cout << endl;


    //使用STL的accumulate(累加)算法

    int result = accumulate(listOne.begin(), listOne.end(),0);

    cout<<"Sum="<<result<<endl;

    cout<<"------------------"<<endl;


    //--------------------------

    //用list容器处理字符型数据

    //--------------------------


    //用LISTCHAR创建一个名为listOne的list对象

    LISTCHAR listTwo;

    //声明i为迭代器

    LISTCHAR::iterator j;


    //从前面向listTwo容器中添加数据

    listTwo.push_front ('A');

    listTwo.push_front ('B');


    //从后面向listTwo容器中添加数据

    listTwo.push_back ('x');

    listTwo.push_back ('y');


    //从前向后显示listTwo中的数据

    cout<<"listTwo.begin()---listTwo.end():"<<endl;

    for (j = listTwo.begin(); j != listTwo.end(); ++j)

        cout << char(*j) << " ";

    cout << endl;


    //使用STL的max_element算法求listTwo中的最大元素并显示

    j=max_element(listTwo.begin(),listTwo.end());

    cout << "The maximum element in listTwo is: "<<char(*j)<<endl;

}


shared_ptr:它是一种引用计数型智能指针,shared_ptr是可以共享所有权的智能指针。它的复制行为相比auto_ptr要正常许多,它也可以被自由用于STL容器中。

shared_ptr在最新的c++11中,已经被列入了标准指针,而auto_ptr则出局了。

说了那么多,shared_ptr采用RAII技术,是防止内存泄露的神器。


但是shared_ptr类不属于标准C++库,而是属于tr1库(C++ tr1是针对C++标准库的第一次扩展。即将到来的下一个版本的C++标准c++0x会包括它,以及一些语言本身的扩充),现在的编译器对于tr1库的支持良莠不齐,但是从gcc 4.0开始就实现了对于shared_ptr的支持。shared_ptr的用法和auto_ptr类似

# g++ -g -o test_shared_ptr test_shared_ptr.cpp

#./test_shared_ptr


#include <tr1/memory>  // g++ 中带着tr1库,不用另外下载

#include <iostream>

#include<vector>

using namespace std;

using std::tr1::shared_ptr;         

class MyClass {

public:

   int i;

   MyClass(int s) {i=s;}

   ~MyClass() {cout<<"This class has been destroied. "<< i <<endl;}

   void myFunc() {cout<<"myFunc() done. "<< i <<endl;}

};            


int main() {


//下面分别建立两个智能指针,然后测试他们的基本使用

//注意不能使用如下形式: shared_ptr<MyClass> ptr = new MyClass(2);

   shared_ptr<MyClass> ptr1(new MyClass(1));            

   shared_ptr<MyClass>ptr2(new MyClass(2));

   (*ptr1).myFunc();          

   ptr2->myFunc();

   cout<<"test 1 done!"<<endl;

    

    

     //下面尝试复制操作,并进行把两个

   ptr2 = ptr1;

   ptr2->myFunc();          

   ptr1->myFunc();

   ptr1.reset();

   cout<<"ptr1.reset() done!"<<endl;

   ptr2.reset();

   cout<<"test 2 done!"<<endl; 

     

      

   MyClass* temp_ptr=new MyClass(3);

   ptr1.reset(temp_ptr);//把普通指针委托给智能指针进行托管          

   ptr1->myFunc(); //注意委托之后不要使用delete,否则程序会出现异常,轻则出错,重则挂掉

    //delete temp_ptr;         

   cout<<"test 3 done"<<endl;

  

  

   //智能指针也可以放入STL容器中,并且不影响其使用

   //注意这里MyClass> 后面有一个空格,否则会被当作一个>>运算符

   vector<shared_ptr<MyClass> > myVector;

         {

     shared_ptr<MyClass> temp_shared_ptr(new MyClass(4));

    myVector.push_back(temp_shared_ptr);

         }//离开temp_shared_ptr的作用域,只是它自己析构,MyClass并不会析构

  

   vector<shared_ptr<MyClass> >::iterator itor =myVector.begin();


   (*itor)->myFunc();

   myVector.clear();

   cout<<"test 4 done!"<<endl;


   return 0;

}

运行结果如下:
myFunc() done. 1
myFunc() done. 2
test 1 done!
This class has been destroied. 2
myFunc() done. 1
myFunc() done. 1
ptr1.reset() done!
This class has been destroied. 1
test 2 done!
myFunc() done. 3
test 3 done
myFunc() done. 4
This class has been destroied. 4
test 4 done!
This class has been destroied. 3

从运行结果中也可以看出shared_ptr 具有很好的资源管理的能力,可以实现理想的复制操作,并且可以和STL容器兼容。在多线程情况下shared_ptr可以达到和c++内置类型同等的安全性。无疑shared_ptr类将是tr1中最常使用的类型。

但是shared_ptr并不是尽善尽美的,它还存在环状引用等问题。


# include <iostream>

 # include <tr1/memory>

 using namespace std;

 class A {

 public:

     A() {

         cout << "construct A!!!" << endl;

     }

     ;

     ~A() {

         cout << "destruct A!!!" << endl;

     }

     ;

 };

 class B: public A {

 public:

     B() {

         cout << "construct B!!!" << endl;

     }

     ;

     ~B() {

         cout << "destruct B!!!" << endl;

     }

     ;

 };

 int main() {

//     B* ptrB0 = new B(); // 普通的B类对象的指针, 发现仅仅运行了A类的构造函数和析构函数; B类对象的指针ptrB0指向的heap内存上的对象并没有被销毁

// 应该知道栈内存上的对象ptrB0不用销毁,应该知道所有的类对象都在堆内存中, delete就是根据ptrB0销毁ptrB0  所指向的对内存上的对象以释放内存

     std::tr1::shared_ptr<B> ptrB1(new B);

 }



  评论这张
 
阅读(930)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2016