Boost使用笔记(Smart_ptr)
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关键字释放,否则可能造成内存泄露导致内存分配失败错误。为了避免这样的错误人们发明了智能指针,其设计思想是管理内存生命周期,使那些从堆中分配的内存在不使用时自动被释放,程序员只需要知道在哪里分配内存,而不用担心是否忘记将其释放。简单的智能指针原理请看下面代码:
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_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基本相同。
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)。看下面例子:
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,若此时再次引用将产生崩溃
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::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:
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管理内部类型(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的引用计数被减一
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_array。shared_array与shared_ptr在使用上除了管理内容的区别,使用方法基本可以视为相同,因此不再赘述。
另一方面,引用计数型智能指针还有一个需要注意的问题就是循环引用问题,对象互相引用形成类似“死锁”的状态,使得指针引用计数不可能减为0,导致内存不能释放,请看下面例子:
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变量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即可,例如做如下修改:
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!
去除了循环引用,CA、CB对象都得到了释放,但需要注意此时使用mA成员的时候需要调用weak_ptr<CA>::lock()函数获取shared_ptr<CA>对象进行相关操作。
以上是使用weak_ptr解决循环引用问题,weak_ptr的另一个作用是保存对象的this指针。某个被shared_ptr管理的类,在某些方法里可能会有类似return this的操作,如下面的GetObj()方法:
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);又如何呢?
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在实际使用中可能是如下形式:
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返回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对象即可:
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类的部分代码:
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_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()方法:
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_即为保存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)的原语,其模拟代码如下:
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是平台相关的,在Linux下GCC提供了__sync_bool_compare_and_swap函数族来实现CAS原语,在Windows下可以使用InterlockedCompareExchange及相关函数实现CAS。使用CAS原语的条件下实现“加一”操作见如下代码,由于__sync_bool_compare_and_swap是原子的,所以加一操作是安全的。
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-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
posted on 2013-07-01 16:33 dangerman 阅读(6329) 评论(0) 编辑 收藏 举报