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

原文地址:https://www.cnblogs.com/dangerman/p/3165111.html