zoukankan      html  css  js  c++  java
  • Boost使用笔记(Smart_ptr)

    我是Word写的,复制过来实在懒得在排版了,有兴趣的朋友可以去我的百度文库看,谢谢 http://wenku.baidu.com/view/34e485e2f61fb7360b4c653e.html

    Boost使用笔记(Smart_ptr

    概述

    Boost库是一个功能强大、构造精巧、跨平台、开源免费的C++程序库,提供了代码编写中所需要的几乎所有常见工具,例如智能指针、bind、正则表达式、xml解析等工具。其代码以泛型编程为基础,且绝大部分代码放在扩展名为hpp的头文件中,以内联的方式引入到目标程序,因此Boost库几乎无需编译即可使用。最新版的C++标准中已经将boost部分模块纳入其中,足见其功能的强大。目前Boost库是除STL库以外最常用的C++代码库之一。

    在实际开发中,Boost多用于应用软件和游戏编程,由于代码量相当庞大,且内部各模块互相引用牵连,致使使用Boost中很小的功能,也要将整个Boost库全部安装,应用上相对冗余,不过由于Boost以泛型编程为基础,实际编译到目标程序中的代码量并不大,且多为Inline形式,效率上也同样不差。

    Boost是跨平台的,其代码支持WinLinuxVxworks等平台,由于精力和时间有限没有对完整的库在Vxworks下进行验证,经过试验的库有3个:

    smart_ptr

    xpressive 

    property_tree

    三个库在Vxworks6.4Vxworks6.8上都做过实验,并且在板卡上试验了Boost的兼容性及性能。在实验中smart_ptr库在Vxworks6.4Vxworks6.8平台下均可编译执行,由于smart_ptr模块相对其他模块较为独立,现已将其从Boost库中全部抽取出来(大概218个文件)上传到Git中,可以在编码中独立使用。

     https://github.com/guolisen/BoostSmartPtr.git

    需要注意的是BoostSmartPtrBoost 1.43.0代码而来,目前只支持shared_ptr。前面介绍过boost库代码互相牵连,即使加入一个weak_ptr也需要再加入关联的好几百个文件,因此为保证精简性没有将其加入。

    xpressive 库是一个用来解析正则表达式的库,由于非常“高级”且庞大,在实验中只进行了基本的编译和使用,没有做过多的尝试。xpressive 库接口简易,功能强大,但同样由于内部牵连过多,因此没能将其抽取出来。

    property_tree库已经编译通过但使用中出现崩溃的情况,没有深究崩溃的原因(有可能是编译环境的问题,非代码本身问题),XML解析使用tinyXml已经完全满足要求。

    智能指针

    在我们日常编码中经常使用到new关键字分配内存,被分配的内存需要在适当的时候调用delete关键字释放,否则可能造成内存泄露导致内存分配失败错误。为了避免这样的错误人们发明了智能指针,其设计思想是管理内存生命周期,使那些从堆中分配的内存在不使用时自动被释放,程序员只需要知道在哪里分配内存,而不用担心是否忘记将其释放。简单的智能指针原理请看下面代码:


     1 template <typename T>
     2 class simple_smart_ptr
     3 {
     4 public:
     5     simple_smart_ptr(T* mem_ptr):mPtr(mem_ptr)
     6     {
     7         assert(mPtr);
     8         std::cout << "Ptr Create!" << std::endl;
     9     };
    10 
    11     ~simple_smart_ptr()
    12     {
    13         assert(mPtr);
    14         std::cout << "Ptr Destory!" << std::endl;
    15         delete mPtr;
    16     };
    17 
    18     T* operator-> () const 
    19     {
    20         assert(mPtr);
    21         return mPtr;
    22     }
    23 
    24 private:
    25     T* mPtr;
    26 };
    27 
    28 class Test
    29 {
    30 public:
    31     void print()
    32     {
    33         std::cout << "HeiHei!" << std::endl;
    34     };
    35 };
    36 
    37 void TestFun()
    38 {
    39     simple_smart_ptr<Test> t(new Test);
    40 
    41     t->print();
    42 }
    43 
    44 int main()
    45 {
    46     TestFun();
    47 
    48     return 0;
    49 }

    输出:

    Ptr Create!

    HeiHei!

    Ptr Destory!

     

    上面是一个智能指针的原型代码,simple_smart_ptr在构造函数获取需要管理的堆指针,即new出来的指针地址。当智能指针结束生命期后,析构函数被调用,被管理的内存被自动释放。

    智能指针是一种防止内存泄露的有效手段,甚至可以说是大型软件开发的必用工具。目前使用最广泛的智能指针是std::auto_ptrboost::smart_ptrstd::auto_ptr出自标准库,不支持引用计数,与STL容器不兼容,在使用上有一定局限。boost::smart_ptrBoost库的一部分,包括scoped_ptrshared_ptrweak_ptr等。Boost的智能指针代码非常优秀,且已经收录到C++最新标准之中,可以放心使用,在下面的章节中会逐步为大家介绍。

    std::auto_ptr

    std::auto_ptr是标准库中提供的一种智能指针,实现了最基本的内存自动管理机制,其使用方法和上一节用到的simple_smart_ptr基本相同。


     1 #include <memory>
     2 class Test
     3 {
     4 public:
     5     void print()
     6     {
     7         std::cout << "HeiHei!" << std::endl;
     8     };
     9 };
    10 
    11 
    12 int main()
    13 {
    14     std::auto_ptr<Test> at(new Test);
    15     at->print();
    16     return 0;
    17 }

    输出:

    HeiHei!

    例子程序中使用std::auto_ptr管理在堆中分配的Test对象,当main函数返回的时候,at局部变量结束生命期,析构函数被调用,Test对象的内存自动释放。

    由于std::auto_ptr没有实现引用计数机制,如果出现两个std::auto_ptr同时引用同一片内存,将会出现毁灭性的结果。因为此时若其中任何一个指针退出生存周期将会释放对应内存区域,与他拥有相同内存指针的另一个std::auto_ptr将变成“野指针”,若此std::auto_ptr退出生命周期系统将崩溃。

    为了解决这样的问题std::auto_ptr引入了一种叫做“拥有权”的概念,每个需要被管理的原始内存指针只对应一个std::auto_ptr,同一时间只有一个std::auto_ptr对此原始指针有“拥有权”。若对此std::auto_ptr执行复制或将其赋值给其他std::auto_ptr,那么原始指针的“拥有权”将转移到被复制的新std::auto_ptr中(即新std::auto_ptr将拥有原始指针,被复制的std::auto_ptr将指向NULL)。看下面例子:


     1 void testFun(std::auto_ptr<Test> p)
     2 {
     3     p->print();
     4 }
     5 
     6 int main()
     7 {
     8 /////////////////////////////////////////////////
     9     std::auto_ptr<Test> b(new Test);
    10     std::auto_ptr<Test> c;
    11     b->print();
    12     c = b; //使用operator=使拥有权转移,b不再拥有Test的指针且指向NULL
    13     c->print();
    14     b->print(); //这里系统将奔溃
    15 
    16     /////////////////////////////////////////////////
    17     std::auto_ptr<Test> d(new Test);
    18     d->print();
    19     std::auto_ptr<Test> e(d);  //使用构造函数使拥有权转移, d不再拥有Test的指针且指向NULL
    20     e->print();
    21     d->print(); //这里系统将奔溃
    22 
    23     /////////////////////////////////////////////////
    24     std::auto_ptr<Test> f(new Test);
    25     f->print();
    26     testFun(f); //使用拷贝构造函数使拥有权转移,f不再拥有Test的指针且指向NULL
    27     f->print(); //这里系统将奔溃
    28 
    29     return 0;
    30 }

    由上面例子可以看到,当std::auto_ptr发生复制,构造,拷贝构造时std::auto_ptr对原始指针的拥有权将转移,自身将指向NULL,此时再引用此std::auto_ptr进行指针操作时系统将会崩溃(此时已经指向NULL)。拥有权的设计避免了std::auto_ptr指向共享区域从而导致二次释放的问题,同时也规避了线程安全问题(无共享区域)。

    std::auto_ptr在使用中还是有许多坑需要注意:

    1. std::auto_ptr用作函数的参数或返回值时需要格外小心,当std::auto_ptr做函数的非引用参数时,由于会调用拷贝构造函数,因此会发生拥有权的转移,此时做参数的std::auto_ptr将指向NULL,若此时再次引用将产生崩溃


     1 void testFun(std::auto_ptr<Test> p)
     2 {
     3     p->print();
     4 }
     5 
     6 int main()
     7 {
     8     Test* pa = new Test;
     9     std::auto_ptr<Test> at(pa);
    10     at->print();
    11     testFun(at); //拥有权转移
    12     at->print(); //拥有权已经转移,再次引用将崩溃
    13 
    14     return 0;
    15 }

    2. std::auto_ptr本身与STL容器不兼容,因此不能将其放到std::vectorstd::liststd::map中使用。(但是VC6貌似可以编译通过,足见VC6已经不适合现代开发了,继续使用将造成巨大的移植隐患)

    3. std::auto_ptr不能管理数组指针,因为在析构的时候std::auto_ptr使用的是delete而不是delete []

    可以看到,使用std::auto_ptr还是有很多不方便的地方,且存在很多极容易出错的坑,这也是std::auto_ptr没有被大规模应用的原因,在下一节中我们将介绍Boost的智能指针shared_ptrshared_ptr是一种建立在引用计数框架下的智能指针,且效率及稳定性极高,不存在兼容性的问题,是应用最广泛的智能指针之一。

     

    boost::shared_ptr

    boost::shared_ptr智能指针使用引用计数管理原始指针,其管理方式有些类似COM组件,当指针的使用者增加时,引用计数器加一,当指针退出生命周期时,指针引用者减少时,计数器自动减一。当计数器被减为零时boost::shared_ptr自动释放所指向的内存。boost::shared_ptr在使用上几乎与普通指针无任何区别,且自带内存回收机制,程序员只需关心在哪里new对象,而不用关心何时释放内存,最大程度上避免了内存泄露的出现。下面我们举例说明如何使用boost::shared_ptr

     1 #include <boost/shared_ptr.hpp>
     2 class Test
     3 {
     4 public:
     5     Test()
     6     {
     7         std::cout << "Test Create!" << std::endl;
     8     }
     9     ~Test()
    10     {
    11         std::cout << "Test Destory!" << std::endl;
    12     }
    13 
    14     void print()
    15     {
    16         std::cout << "HeiHei!" << std::endl;
    17     };
    18 };
    19 
    20 void func(boost::shared_ptr<Test> p)
    21 {
    22     //进入函数时引用计数加一变为2
    23     p->print();
    24 }//函数返回时引用计数再次减一变为1,因此不会释放内存
    25 
    26 int main()
    27 {
    28     /////////////////////////////////////////////////
    29     //内部类型的例子
    30 /////////////////////////////////////////////////
    31     std::cout << "<<< Example 1 >>>" << std::endl;
    32     boost::shared_ptr<int> pi(new int(100));
    33     std::cout << "pi: " << *pi << std::endl;
    34     *pi = 200;
    35     std::cout << "pi: " << *pi << std::endl << std::endl;
    36 
    37     /////////////////////////////////////////////////
    38     //对象类型的例子
    39     /////////////////////////////////////////////////
    40     std::cout << "<<< Example 2 >>>" << std::endl;
    41     {
    42         boost::shared_ptr<Test> po(new Test); //引用计数为1
    43 
    44         {
    45             boost::shared_ptr<Test> po2(po); //引用计数加一,变为2
    46             std::cout << "After Create var 'po2' po: " 
    47                         << po.use_count() 
    48                         << " po2: " 
    49                         << po2.use_count() 
    50                         << std::endl; 
    51             std::cout << "Use po: ";
    52             po->print();
    53             std::cout << "Use po2: ";
    54             po2->print();
    55         }   //这里退出时,引用计数减一变为1,因此不释放内存
    56         
    57     }//退出生存期时引用计数再次减一变为0,释放内存
    58     std::cout << std::endl;
    59 
    60     /////////////////////////////////////////////////
    61 //做参数的例子
    62 /////////////////////////////////////////////////
    63     std::cout << "<<< Example 3 >>>" << std::endl;
    64     {
    65         boost::shared_ptr<Test> pp(new Test);//引用计数为1
    66         func(pp);
    67     }//退出生存期时引用计数再次减一变为0,释放内存
    68     std::cout << std::endl;
    69 
    70     /////////////////////////////////////////////////
    71     //容器例子
    72     /////////////////////////////////////////////////
    73     std::cout << "<<< Example 4 >>>" << std::endl;
    74     typedef boost::shared_ptr<Test> TEST_PTR;
    75     typedef std::vector<TEST_PTR>   TEST_VEC;
    76     {
    77         TEST_PTR pv(new Test); 
    78         TEST_VEC vec;
    79         vec.push_back(pv); //引用计数加一,变为2
    80         vec[0]->print();
    81     }//退出生存期时pv,vec分别析构,两次减一,使引用计数变为0,释放内存
    82 
    83     return 0;
    84 }

    输出:

    <<< Example 1 >>>

    pi: 100

    pi: 200

    <<< Example 2 >>>

    Test Create!

    After Create var 'po2' po: 2 po2: 2

    Use po: HeiHei!

    Use po2: HeiHei!

    Test Destory!

    <<< Example 3 >>>

    Test Create!

    HeiHei!

    Test Destory!

    <<< Example 4 >>>

    Test Create!

    HeiHei!

    Test Destory!

    如上的例子中Example1说明了如何利用share_ptr管理内部类型(charintfloat…),Example2处理对象类型,Example3share_ptr用作函数参数,Example4展示shared_ptr如何运用在容器中。当然share_ptr还可以做成员变量等,原理相同,这里不再赘述。

    share_ptr在使用上和普通指针几乎没有区别,因为share_ptr内部引用计数机制记录着所有对原始指针的引用数(即有多少地方在用这个指针),当某个引用的指针不再使用了(退出生命周期),share_ptr会自动将引用计数减一,当所有引用此指针的地方均退出时(引用计数变为0),说明此指针已没有使用的必要,share_ptr将自动将其释放。

    share_ptr在使用中每一次的复制传递都会使share_ptr内部的引用计数改变,通俗一点的解释(当然并不准确,只是为了理解)就是当shared_ptr构造函数、拷贝构造、operator=被调用的时候计数器会加一(若有原引用对象,则原引用对象减一),析构函数被调用的时候会减一,总结一下引用计数的变化情况如下:

    Ø 计数器加一

    1.       share_ptr对象建立时引用计数自动加一,如例Example1

    2.       由原share_ptr对象创建新share_ptr对象时引用计数自动加一,如例Example2建立指针po2

    3.       做函数参数时引用计数自动加一,如例Example3

    4.       做容器中的元素时计数自动加一,如例Example4pvpushvector后计数器变为2

    Ø 计数器减一

    1.       share_ptr离开作用域,share_ptr析构函数被调用使引用计数减一

    2.       share_ptr被复制新值时,原引用对象将被减一,如Example2po2po同时管理同一块内存区域,若po2被其他新share_ptr赋值,则po的引用计数被减一

    1 boost::shared_ptr<Test> po(new Test);
    2 boost::shared_ptr<Test> po2(po); //引用计数加一,变为2
    3 boost::shared_ptr<Test> poo(new Test);
    4 po2 = poo;
    5 std::cout << po2.use_count() << std::endl; //po2 与poo同时引用一块区域
    6 std::cout << po.use_count() << std::endl;  //1 原指针被减一

    3.       share_ptr做容器的元素,当从容器中将share_ptr删除时引用计数减一

     

    看起来稍有些复杂,大家可以通过use_count()返回引用计数的方法观察share_ptr指针的计数变化情况,在实际使用中几乎不用关心引用计数的情况shared_ptr会保证内存的正确释放

    以上介绍了shared_ptr的基本使用方法,在实际使用中还需要注意两个问题,第一,shared_ptr不能处理数组元素,即new[]出来的数据不能放到shared_ptr管理,原因与std:auto_ptr相同,shared_ptr在析构的时候使用的是delete而不是delete[],如果需要管理数组元素需要使用shared_arrayshared_arrayshared_ptr在使用上除了管理内容的区别,使用方法基本可以视为相同,因此不再赘述。

             另一方面,引用计数型智能指针还有一个需要注意的问题就是循环引用问题,对象互相引用形成类似“死锁”的状态,使得指针引用计数不可能减为0,导致内存不能释放,请看下面例子:


     1 #include <boost/shared_ptr.hpp>
     2 
     3 class CB;
     4 class CA;
     5 
     6 class CA
     7 {
     8 public:
     9     CA()
    10     {
    11         std::cout << "CA Create!" << std::endl;
    12     }
    13     ~CA()
    14     {
    15         std::cout << "CA Destory!" << std::endl;
    16     }
    17 
    18     void print()
    19     {
    20         std::cout << "CA mSP Ref Count: " << mB.use_count() << std::endl;
    21     }
    22 
    23 public:
    24     boost::shared_ptr<CB> mB;
    25 };
    26 
    27 class CB
    28 {
    29 public:
    30     CB()
    31     {
    32         std::cout << "CB Create!" << std::endl;
    33     }
    34     ~CB()
    35     {
    36         std::cout << "CB Destory!" << std::endl;
    37     }
    38 
    39     void print()
    40     {
    41         std::cout << "CB mSP Ref Count: " << mA.use_count() << std::endl;
    42     }
    43 
    44 public:
    45     boost::shared_ptr<CA> mA;
    46 };
    47 
    48 void func()
    49 {
    50     boost::shared_ptr<CA> a(new CA); //建立时引用计数为1
    51     boost::shared_ptr<CB> b(new CB); //建立时引用计数为1
    52     std::cout << "a use_count: " << a.use_count() << std::endl;
    53     std::cout << "b use_count: " << b.use_count() << std::endl;
    54 
    55     a->mB = b; 
    56     b->mA = a;
    57     a->print(); //互相引用后,a和b的引用计数都变为二
    58     b->print();
    59 
    60     std::cout << "> a use_count: " << a.use_count() << std::endl;
    61     std::cout << "> b use_count: " << b.use_count() << std::endl;
    62 }
    63 
    64 int main()
    65 {
    66     func();
    67     return 0;
    68 }

    输出:

    CA Create!

    CB Create!

    a use_count: 1

    b use_count: 1

    CA mSP Ref Count: 2

    CB mSP Ref Count: 2

    > a use_count: 2

    > b use_count: 2

    func()函数中shared_ptr变量ab互相引用,使得自身的引用计数都变成了二,当函数返回的时候,局部变量ab被析构,但由于析构后引用计数为1,不会释放内部的CACB对象,因此CACB对象也不会调用自身的析构函数释放mAmBab的引用计数不会被减为零,这样就造成了ab的内存泄露。循环引用问题是引用计数型智能指针的常见问题,解决办法就是使用另一种辅助指针weak_ptr

     

    boost::weak_ptr

    weak_ptrshared_ptr指针的辅助工具,由shared_ptr指针或其他weak_ptr指针构造产生,其本质是一种弱引用指针,即weak_ptr在使用中不会修改对应shared_ptr指针的引用计数值,也没有对“*”和“->”进行重载,weak_ptr接口非常简单,通常会用到如下两个:

    expired()返回当前引用计数是否为0Bool值(use_count() == 0),即当前weak_ptr所指向的shared_ptr是否可用。

    lock()weak_ptr所指向的shared_ptr指针可用则将其返回,否则返回一个指向NULLshared_ptrexpired()? shared_ptr<T>(): shared_ptr<T>(*this)

    从这两个接口可以看到weak_ptr基本处于一种“观察者”的角色。weak_ptr不能管理引用计数及内存的释放时机,但却可以知道shared_ptr是否已经被释放(见lock()),在实际使用中weak_ptr可以帮助shared_ptr解决很多问题,例如上节的循环引用。

    使用weak_ptr解决循环引用问题,只需将上例中任意一个类的成员改成weak_ptr即可,例如做如下修改:

     

     1 class CB
     2 {
     3 public:
     4     CB()
     5     {
     6         std::cout << "CB Create!" << std::endl;
     7     }
     8     ~CB()
     9     {
    10         std::cout << "CB Destory!" << std::endl;
    11     }
    12 
    13     void print()
    14     {
    15         std::cout << "CB mSP Ref Count: " << mA.use_count() << std::endl;
    16     }
    17 
    18 public:
    19     boost::weak_ptr<CA> mA;//将原来的shared_ptr<CA>改为weak_ptr<CA>
    20 };

    只需将mA成员的类型由原来的shared_ptr<CA>类型修改为weak_ptr<CA>,由于weak_ptr<CA>不改变shared_ptr<CA>的引用计数,因此不会造成循环引用问题,重新编译运行结果如下:

    输出:

    CA Create!

    CB Create!

    a use_count: 1

    b use_count: 1

    CA mSP Ref Count: 2

    CB mSP Ref Count: 1      //这里引用计数已经不是2了,所以不会造成循环引用。

    > a use_count: 1

    > b use_count: 2

    CA Destory!

    CB Destory!

     

    去除了循环引用,CACB对象都得到了释放,但需要注意此时使用mA成员的时候需要调用weak_ptr<CA>::lock()函数获取shared_ptr<CA>对象进行相关操作。

    以上是使用weak_ptr解决循环引用问题,weak_ptr的另一个作用是保存对象的this指针。某个被shared_ptr管理的类,在某些方法里可能会有类似return this的操作,如下面的GetObj()方法:


     1 class TestA
     2 { 
     3 public
     4     TestA() { 
     5         cout << "TestA::TestA()" << endl; 
     6     } 
     7      
     8     ~TestA() { 
     9         cout << "TestA::~TestA()" << endl; 
    10     } 
    11      
    12     TestA* GetObj() { 
    13         return this;
    14     } 
    15      
    16 };

    很明显,直接返回this指针会使得此对象失控(可能在任何地方被delete),但如果让GetObj()返回一个sherad_ptr<TestA>(this);又如何呢?


     1 class TestA
     2 { 
     3 public
     4     TestA() { 
     5         std::cout << "TestA::TestA()" << std::endl; 
     6     } 
     7      
     8     ~TestA() { 
     9         std::cout << "TestA::~TestA()" << std::endl; 
    10     } 
    11      
    12     shared_ptr<TestA> GetObj() { 
    13 std::cout << "TestA::GetObj()" << std::endl;
    14         return shared_ptr<TestA>(this);
    15     } 
    16 };

    看似this指针在shared_ptr指针的管辖下是安全的,实则并非如此,因为TestA在实际使用中可能是如下形式:


    1 void func()
    2 {
    3     boost::shared_ptr<TestA> obj_a (new TestA);
    4     boost::shared_ptr<TestA> p = obj_a->GetObj();
    5 }
    6

    也就是说this指针被两个引用计数为一的shared_ptr对象管理,当func函数返回的时候就会造成二次释放,通俗的讲就是要崩溃。

    输出:

    TestA::TestA()

    TestA::GetObj()

    TestA::~TestA()

    TestA::~TestA()    //第二次释放崩溃了

     

    如果解决上面问题比较理想的办法是在shared_ptr构造的时候,将其保存在被管理对象中的一个weak_ptr成员变量中,这样在需要的时候只要通过weak_ptr返回thisshare_ptr即可。

    根据如上解决思路boost提供了一个叫做enable_shared_from_this工具类,enable_shared_from_this中含有一个保存对象this指针的weak_ptr成员,weak_ptr成员在shared_ptr构造的时候被初始化,此时weak_ptr保存了外部的shared_ptr对象,在后面使用this的时候只需要通过weak_ptr构造shared_ptr返回即可。

    TestA为例,我们的TestA类只需要继承enable_shared_from_this类,在需要返回this的地方使用enable_shared_from_this的方法shared_from_this返回thisshared_ptr对象即可:


     1 #include <boost/smart_ptr.hpp>
     2 #include <boost/enable_shared_from_this.hpp>
     3 
     4 class TestA: public boost::enable_shared_from_this<TestA> 
     5 { 
     6 public
     7     TestA()
     8     { 
     9         std::cout << "TestA::TestA()" << std::endl; 
    10     } 
    11      
    12     ~TestA() 
    13     { 
    14         std::cout << "TestA::~TestA()" << std::endl; 
    15     } 
    16      
    17     boost::shared_ptr<TestA> GetObj() 
    18     { 
    19         std::cout << "TestA::GetObj()" << std::endl;
    20         boost::shared_ptr<TestA> p = shared_from_this(); 
    21         return p;
    22     } 
    23      
    24 }; 
    25  
    26 void func()
    27 {
    28     boost::shared_ptr<TestA> obj_a(new TestA); 
    29     boost::shared_ptr<TestA> p = obj_a->GetObj(); 
    30 }

    输出:

    TestA::TestA()

    TestA::GetObj()

    TestA::~TestA()

    下面是enable_shared_from_this类的部分代码:

     1 template<class T> 
     2 class enable_shared_from_this
     3 {
     4     //other method...
     5 
     6 public:
     7     shared_ptr<T> shared_from_this()
     8     {
     9         shared_ptr<T> p( weak_this_ );
    10         BOOST_ASSERT( p.get() == this );
    11         return p;
    12     }
    13 
    14 private:
    15     mutable weak_ptr<T> weak_this_;
    16 };

    由于enable_shared_from_thisweak_ptr成员在obj_a对象建立的时候就已经将其“记录”,所以在使用shared_from_this的时候,返回的是和obj_a对象“相同”的shared_ptr,因此不会造成二次释放。

    总结一下,当被shared_ptr管理类的对象需要获取当前管理自己的shared_ptr的时候,需要继承enable_shared_from_this类,通过shared_from_this获取shared_ptr对象,不要自己通过构造shared_ptr<TestA>(this)来实现(会造成二次释放)。

    enable_shared_from_this类非常简单,但看过源码的人恐怕会有一个疑问,enable_shared_from_this的构造函数是个空函数,其内部的weak_ptr成员是何时被this赋值的呢?

    其实赋值过程非常简单,由于篇幅有限在这里只简要说明。对于上例来说,weak_ptr被赋值确实是在obj_a对象建立的时候,只不过不在weak_ptr的构造函数,而是在shared_ptr的构造函数,在shared_ptr的构造函数中会回调enable_shared_from_this对象的_internal_accept_owner()方法:


    1 template<class X, class Y>
    2 void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const
    3 {
    4     if( weak_this_.expired() )
    5     {
    6         weak_this_ = shared_ptr<T>( *ppx, py );
    7     }
    8 }

    weak_this_即为保存thisweak_ptr对象,回调后weak_this_已被初始化。至于shared_ptr是如何回调的还是请大家自行阅读shared_ptr源码。

    线程安全问题

    线程安全可能是很多使用者比较关心的问题,对于std::auto_ptr来讲,由于同一时间只有一个std::auto_ptr对象有原始指针的“拥有权”,不存在多std::auto_ptr的共享资源,因此不需要考虑std::auto_ptr的线程安全问题。

    boost::shared_ptr由于使用了引用计数机制,多个boost::shared_ptr对象可能引用同一个计数器对象,必然会引入线程安全问题。从boost1.33.0版本开始boost::shared_ptr使用了Lock-Free机制保证线程安全。

    在多线程程序中,通常使用锁来保证共享数据安全,但过多的使用锁会带来线程阻塞,死锁等问题。使用锁过多的程序会使多线程的效率大大降低,极端情况下甚至可能低于单线程程序,因此可以在一些场景中使用Lock-Free方式避免锁的使用。

    Lock-Free(无锁编程)为一种多线程程序的编程技巧,它可以在不使用互斥锁解的前提下解决线程安全问题。举例来说,一个webserver使用多线程处理每个外部请求,为了记录webserver处理了多少次访问,在每次请求结束后,线程可能对一个记录请求次数的全局变量进行加一操作。在此场景中,全局变量为共享数据,多线程程序需要加锁来保证数据安全,但实际上对此变量的操作只是一条“加一”操作而已,若能保证“加一”操作的原子性,即可在无锁的前提下保证线程安全。

    为了保证“加一”操作的原子性,需要使用一种叫做CAScompare-and-swap)的原语,其模拟代码如下:


     1 bool CAS(intptr_t* addr, intptr_t oldv, intptr_t newv) atomically 
     2 {
     3     if((*addr) == oldv) 
     4     {
     5         *addr = newv;
     6         return true;
     7     }
     8     else
     9     {
    10         return false;
    11     }
    12 }

    CAS使用addr指针所指向的内容与oldv比较,若相同则将newv的值赋值到*addr中,且整个过程是原子过程,不可被打断。上面代码只是CAS的原理代码,实际使用可能会是内嵌汇编等方式(在汇编中使用lock指令),很明显CAS是平台相关的,在LinuxGCC提供了__sync_bool_compare_and_swap函数族来实现CAS原语,在Windows下可以使用InterlockedCompareExchange及相关函数实现CAS。使用CAS原语的条件下实现“加一”操作见如下代码,由于__sync_bool_compare_and_swap是原子的,所以加一操作是安全的。


    1 int gCount = 0;
    2 int tmp = 0
    3 
    4 do{
    5     tmp = gCount + 1;
    6 }while(!__sync_bool_compare_and_swap(&gCount, gCount, tmp));
    7 //若参数二的值与参数一指针中的内容相同,则执行gCount = tmp

    使用Lock-Free解决shared_ptr的计数安全问题,从理论上讲是和如上场景是相同的,shared_ptr所使用的引用计数实际上是对变量的“加一”、“减一”操作,因此只要在数值变化时使用CAS,即可保证引用计数的线程安全性。shared_ptr的内部类sp_counted_base为计数实现的核心组件,如果希望了解Lock-Freeshared_ptr中是如何实现的,可以阅读此类,这里不再赘述。

    Lock-Free技巧的核心是使用CAS原语保证赋值操作的原子性,但CAS原语多数是由汇编或内嵌汇编实现,因此Lock-Free技巧是与硬件平台相关的,甚至不同的CPU都可能造成未知的问题。若在某些硬件平台上不能使用Lock-Freeshared_ptr还提供了pthread的互斥方式,即使用pthread接口替代Lock-Free。使用方法是在头文件中加入宏定义:BOOST_SP_USE_PTHREADS

    如果能够确定应用程序不会使用多线程,可以定义宏BOOST_SP_DISABLE_THREADS,关闭所有线程互斥的相关操作,从而提高shared_ptr的使用效率。在boost的官网中有一节介绍boost::shared_ptr的线程安全问题,有兴趣的朋友可以阅读。

    总结

    本文对智能指针的概念及使用方法做了简要的介绍,并对std::auto_ptrboost::shared_ptrboost::weak_ptr做了比较详细的说明,由于boost::scoped_ptrstd::auto_ptr非常相似,因此没有做过多的讲解。

    智能指针是现代C++编程中常用的功能,也是避免内存泄露的完美解决方案,但智能指针在使用中还需要对其优缺点及使用方式做充分了解,否则胡乱使用不但会使内存管理变得一塌糊涂,还会造成系统的不稳定。

    特别说明这里介绍的是boost库中的shared_ptr并非std::tr1::shared_ptr,两种指针基本相同,且std::tr1::shared_ptr是由boost::shared_ptr代码而来,但毕竟是两个不同的库还是应该区别对待。

     

     

    Writen By Dangerman

    Guolisen@gmail.com

    2013-7-1

  • 相关阅读:
    iframe设置背景透明
    苹果新版QuickTime X启用新图标
    css命名规则
    视觉设计前瞻实用性研究(PNVD) 第二期
    Tab(选项卡)的产品设计原则及应用 [1]
    WiFi热点认证
    自画表格,微软报表,水晶报表初用初分析.
    Winform 打印类重温
    Winform 打印DataGrid View
    Winform 常用的.记住免得以后到处找.
  • 原文地址:https://www.cnblogs.com/dangerman/p/3165111.html
Copyright © 2011-2022 走看看