Boost使用笔记(Smart_ptr)

时间:2023-03-09 07:09:24
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是跨平台的,其代码支持Win、Linux、Vxworks等平台,由于精力和时间有限没有对完整的库在Vxworks下进行验证,经过试验的库有3个:

l smart_ptr

l xpressive

l property_tree

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

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

需要注意的是BoostSmartPtr由Boost 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     };

 

     ~simple_smart_ptr()

     {

         assert(mPtr);

         std::cout << "Ptr Destory!" << std::endl;

         delete mPtr;

     };

 

     T* operator-> () const 

     {

         assert(mPtr);

         return mPtr;

     }

 

 private:

     T* mPtr;

 };

 

 class Test

 {

 public:

     void print()

     {

         std::cout << "HeiHei!" << std::endl;

     };

 };

 

 void TestFun()

 {

     simple_smart_ptr<Test> t(new Test);

 

     t->print();

 }

 

 int main()

 {

     TestFun();

 

     return ;

 }

输出:

Ptr Create!

HeiHei!

Ptr Destory!

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

智能指针是一种防止内存泄露的有效手段,甚至可以说是大型软件开发的必用工具。目前使用最广泛的智能指针是std::auto_ptr和boost::smart_ptr,std::auto_ptr出自标准库,不支持引用计数,与STL容器不兼容,在使用上有一定局限。boost::smart_ptr是Boost库的一部分,包括scoped_ptr、shared_ptr、weak_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 };

 

 

 int main()

 {

     std::auto_ptr<Test> at(new Test);

     at->print();

     return ;

 }

输出:

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);

     std::auto_ptr<Test> c;

     b->print();

     c = b; //使用operator=使拥有权转移,b不再拥有Test的指针且指向NULL

     c->print();

     b->print(); //这里系统将奔溃

 

     /////////////////////////////////////////////////

     std::auto_ptr<Test> d(new Test);

     d->print();

     std::auto_ptr<Test> e(d);  //使用构造函数使拥有权转移, d不再拥有Test的指针且指向NULL

     e->print();

     d->print(); //这里系统将奔溃

 

     /////////////////////////////////////////////////

     std::auto_ptr<Test> f(new Test);

     f->print();

     testFun(f); //使用拷贝构造函数使拥有权转移,f不再拥有Test的指针且指向NULL

     f->print(); //这里系统将奔溃

 

     return ;

 }

由上面例子可以看到,当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);

     at->print();

     testFun(at); //拥有权转移

     at->print(); //拥有权已经转移,再次引用将崩溃

 

     return ;

 }

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

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

可以看到,使用std::auto_ptr还是有很多不方便的地方,且存在很多极容易出错的坑,这也是std::auto_ptr没有被大规模应用的原因,在下一节中我们将介绍Boost的智能指针shared_ptr,shared_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()

     {

         std::cout << "Test Destory!" << std::endl;

     }

 

     void print()

     {

         std::cout << "HeiHei!" << std::endl;

     };

 };

 

 void func(boost::shared_ptr<Test> p)

 {

     //进入函数时引用计数加一变为2

     p->print();

 }//函数返回时引用计数再次减一变为1,因此不会释放内存

 

 int main()

 {

     /////////////////////////////////////////////////

     //内部类型的例子

 /////////////////////////////////////////////////

     std::cout << "<<< Example 1 >>>" << std::endl;

     boost::shared_ptr<int> pi(new int());

     std::cout << "pi: " << *pi << std::endl;

     *pi = ;

     std::cout << "pi: " << *pi << std::endl << std::endl;

 

     /////////////////////////////////////////////////

     //对象类型的例子

     /////////////////////////////////////////////////

     std::cout << "<<< Example 2 >>>" << std::endl;

     {

         boost::shared_ptr<Test> po(new Test); //引用计数为1

 

         {

             boost::shared_ptr<Test> po2(po); //引用计数加一,变为2

             std::cout << "After Create var 'po2' po: " 

                         << po.use_count() 

                         << " po2: " 

                         << po2.use_count() 

                         << std::endl; 

             std::cout << "Use po: ";

             po->print();

             std::cout << "Use po2: ";

             po2->print();

         }   //这里退出时,引用计数减一变为1,因此不释放内存

         

     }//退出生存期时引用计数再次减一变为0,释放内存

     std::cout << std::endl;

 

     /////////////////////////////////////////////////

 //做参数的例子

 /////////////////////////////////////////////////

     std::cout << "<<< Example 3 >>>" << std::endl;

     {

         boost::shared_ptr<Test> pp(new Test);//引用计数为1

         func(pp);

     }//退出生存期时引用计数再次减一变为0,释放内存

     std::cout << std::endl;

 

     /////////////////////////////////////////////////

     //容器例子

     /////////////////////////////////////////////////

     std::cout << "<<< Example 4 >>>" << std::endl;

     typedef boost::shared_ptr<Test> TEST_PTR;

     typedef std::vector<TEST_PTR>   TEST_VEC;

     {

         TEST_PTR pv(new Test); 

         TEST_VEC vec;

         vec.push_back(pv); //引用计数加一,变为2

         vec[]->print();

     }//退出生存期时pv,vec分别析构,两次减一,使引用计数变为0,释放内存

 

     return ;

 }

输出:

<<< 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管理内部类型(char、int、float…),Example2处理对象类型,Example3将share_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.       做容器中的元素时计数自动加一,如例Example4,pv被push到vector后计数器变为2

Ø 计数器减一

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

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

 boost::shared_ptr<Test> po(new Test);

 boost::shared_ptr<Test> po2(po); //引用计数加一,变为2

 boost::shared_ptr<Test> poo(new Test);

 po2 = poo;

 std::cout << po2.use_count() << std::endl; //po2 与poo同时引用一块区域

 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_array。shared_array与shared_ptr在使用上除了管理内容的区别,使用方法基本可以视为相同,因此不再赘述。

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

 1 #include <boost/shared_ptr.hpp>

 2 

 3 class CB;

 4 class CA;

 5 

 6 class CA

 7 {

 8 public:

 9     CA()

     {

         std::cout << "CA Create!" << std::endl;

     }

     ~CA()

     {

         std::cout << "CA Destory!" << std::endl;

     }

 

     void print()

     {

         std::cout << "CA mSP Ref Count: " << mB.use_count() << std::endl;

     }

 

 public:

     boost::shared_ptr<CB> mB;

 };

 

 class CB

 {

 public:

     CB()

     {

         std::cout << "CB Create!" << std::endl;

     }

     ~CB()

     {

         std::cout << "CB Destory!" << std::endl;

     }

 

     void print()

     {

         std::cout << "CB mSP Ref Count: " << mA.use_count() << std::endl;

     }

 

 public:

     boost::shared_ptr<CA> mA;

 };

 

 void func()

 {

     boost::shared_ptr<CA> a(new CA); //建立时引用计数为1

     boost::shared_ptr<CB> b(new CB); //建立时引用计数为1

     std::cout << "a use_count: " << a.use_count() << std::endl;

     std::cout << "b use_count: " << b.use_count() << std::endl;

 

     a->mB = b; 

     b->mA = a;

     a->print(); //互相引用后,a和b的引用计数都变为二

     b->print();

 

     std::cout << "> a use_count: " << a.use_count() << std::endl;

     std::cout << "> b use_count: " << b.use_count() << std::endl;

 }

 

 int main()

 {

     func();

     return ;

 }

输出:

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变量a、b互相引用,使得自身的引用计数都变成了二,当函数返回的时候,局部变量a、b被析构,但由于析构后引用计数为1,不会释放内部的CA、CB对象,因此CA、CB对象也不会调用自身的析构函数释放mA、mB,a、b的引用计数不会被减为零,这样就造成了a、b的内存泄露。循环引用问题是引用计数型智能指针的常见问题,解决办法就是使用另一种辅助指针weak_ptr。

boost::weak_ptr

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

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

lock()若weak_ptr所指向的shared_ptr指针可用则将其返回,否则返回一个指向NULL的shared_ptr。(expired()? 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     {

         std::cout << "CB Destory!" << std::endl;

     }

 

     void print()

     {

         std::cout << "CB mSP Ref Count: " << mA.use_count() << std::endl;

     }

 

 public:

     boost::weak_ptr<CA> mA;//将原来的shared_ptr<CA>改为weak_ptr<CA>

 };

只需将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!

去除了循环引用,CA、CB对象都得到了释放,但需要注意此时使用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; 

     } 

      

     TestA* GetObj() { 

         return this;

     } 

      

 };

很明显,直接返回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; 

     } 

      

     shared_ptr<TestA> GetObj() { 

 std::cout << "TestA::GetObj()" << std::endl;

         return shared_ptr<TestA>(this);

     } 

 };

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

 void func()

 {

     boost::shared_ptr<TestA> obj_a (new TestA);

     boost::shared_ptr<TestA> p = obj_a->GetObj();

 }

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

输出:

TestA::TestA()

TestA::GetObj()

TestA::~TestA()

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

如果解决上面问题比较理想的办法是在shared_ptr构造的时候,将其保存在被管理对象中的一个weak_ptr成员变量中,这样在需要的时候只要通过weak_ptr返回this的share_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返回this的shared_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; 

     } 

      

     ~TestA() 

     { 

         std::cout << "TestA::~TestA()" << std::endl; 

     } 

      

     boost::shared_ptr<TestA> GetObj() 

     { 

         std::cout << "TestA::GetObj()" << std::endl;

         boost::shared_ptr<TestA> p = shared_from_this(); 

         return p;

     } 

      

 }; 

  

 void func()

 {

     boost::shared_ptr<TestA> obj_a(new TestA); 

     boost::shared_ptr<TestA> p = obj_a->GetObj(); 

 }

输出:

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_ );

         BOOST_ASSERT( p.get() == this );

         return p;

     }

 

 private:

     mutable weak_ptr<T> weak_this_;

 };

由于enable_shared_from_this的weak_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()方法:

 template<class X, class Y>

 void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const

 {

     if( weak_this_.expired() )

     {

         weak_this_ = shared_ptr<T>( *ppx, py );

     }

 }

weak_this_即为保存this的weak_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对象可能引用同一个计数器对象,必然会引入线程安全问题。从boost的1.33.0版本开始boost::shared_ptr使用了Lock-Free机制保证线程安全。

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

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

为了保证“加一”操作的原子性,需要使用一种叫做CAS(compare-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     {

         return false;

     }

 }

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

 int gCount = ;

 int tmp = ;

 

 do{

     tmp = gCount + ;

 }while(!__sync_bool_compare_and_swap(&gCount, gCount, tmp));

 //若参数二的值与参数一指针中的内容相同,则执行gCount = tmp

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

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

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

总结

本文对智能指针的概念及使用方法做了简要的介绍,并对std::auto_ptr、boost::shared_ptr、boost::weak_ptr做了比较详细的说明,由于boost::scoped_ptr与std::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