内存管理是一个比较繁琐的问题,C++中有两个实现方案: 垃圾回收机制和智能指针。垃圾回收机制因为性能等原因不被C++的大佬们推崇, 而智能指针被认为是解决C++内存问题的最优方案。
1. 智能指针定义
一个智能指针就是一个C++的对象, 这对象的行为像一个指针,但是它却可以在其不需要的时候自动删除。注意这个“其不需要的时候”, 这可不是一个精确的定义。这个不需要的时候可以指好多方面:局部变量退出函数作用域、类的对象被析构……。所以boost定义了多个不同的智能指针来管理不同的场景。
shared_ptr<T> | 内部维护一个引用计数器来判断此指针是不是需要被释放。是boost中最常用的智能指针了。 |
scoped_ptr<t> | 当这个指针的作用域消失之后自动释放 |
intrusive_ptr<T> | 也维护一个引用计数器,比shared_ptr有更好的性能。但是要求T自己提供这个计数器。 |
weak_ptr<T> | 弱指针,要和shared_ptr 结合使用 |
shared_array<T> | 和shared_ptr相似,但是访问的是数组 |
scoped_array<T> | 和scoped_ptr相似,但是访问的是数组 |
2. Boost::scoped_ptr<T>
2.1 定义
scoped_ptr 是boost中最简单的智能指针。scoped_ptr的目的也是很简单, 当一个指针离开其作用域时候,释放相关资源。特别注意的一定就是scoped_ptr 不能共享指针的所有权也不能转移所有权。也就是说这个内存地址就只能给的声明的变量用,不能给其他使用。
2.2 特点
(1)scoped_ptr的效率和空间的消耗内置的指针差不多
(2)scoped_ptr不能用于管理数组对象,不能指向一块能够动态增长的内存区域(用scoped_array代替)
(3)scoped_ptr不能转换所有权,因此不能作为函数的返回值
(4)scoped_ptr不能共享所有权,因此不能用于stl的容器中(用shared_ptr代替)
2.3 使用原则
(1)在可能有异常抛出的作用域里使用指针
(2)函数里有几条控制路径
(3)动态分配对象的生存期应被限制于特定的作用域内
(4)异常安全非常重要时(总应如此!)
2.4 例子
1 class test 2 { 3 public: 4 void print() 5 { 6 cout << "test print now" <<endl; 7 } 8 }; 9 int _tmain(int argc, _TCHAR* argv[]) 10 { 11 boost::scoped_ptr<test> x(new test); 12 x->print(); 13 return 0; 14 }
3.Boost::shared_ptr<T>
3.1 定义
boost::shared_ptr是可以共享所有权的智能指针.
3.2 特点
(1)boost::shared_ptr在内部维护一个引用计数器, 当有一个指针指向这块内存区域是引用计数+1, 反之-1, 如果没有任何指针指向这块区域, 引用计数器为0,释放内存区域。
(2)boost::shared_ptr可以共享和转移所有权
(3)boost::shared_ptr可以被标准库的容器所使用
(4)boost::shared_ptr是线程安全的,这点在多线程程序中也非常重要
(5)boost::shared_ptr不能指向一块动态增长的内存(用share_array代替)
3.3 使用原则
(1)避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放
(2)shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)
(3)不要构造一个临时的shared_ptr作为函数的参数
3.4 例子
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 boost::shared_ptr<test> ptr_1(new test); 4 ptr_1->print();//引用计数为1 5 boost::shared_ptr<test> ptr_2 = ptr_1; 6 ptr_2->print();//引用计数为2 7 ptr_1->print();// 引用计数还是为2 8 return 0; 9 }
4. Boost::intrusive_ptr<T>
4.1 定义
boost::intrusive_ptr一种“侵入式”的引用计数指针,它实际并不提供引用计数功能,而是要求被存储的对象自己实现引用计数功能,并提供intrusive_ptr_add_ref和intrusive_ptr_release函数接口供boost::intrusive_ptr调用。
4.2 使用原则
(1)你需要把 this当作智能指针来使用
(2)已有代码使用或提供了插入式的引用计数
(3)智能指针的大小必须与裸指针的大小相等
4.3 例子
下面通过一个具体的例子来说明boost::intrusive_ptr的用法,首先实现一个基类intrusive_ptr_base,定义intrusive_ptr_add_ref和intrusive_ptr_release函数来提供引用计数功能。
1 /** 2 * intrusive_ptr_base基类,提供intrusive_ptr_add_ref()和intrusive_ptr_release()函数来提供引用计数功能; 3 * 使用boost::intrusive_ptr指针存储的用户类类型必须继承自intrusive_ptr_base基类。 4 */ 5 #include <ostream> 6 #include <boost/checked_delete.hpp> 7 #include <boost/detail/atomic_count.hpp> 8 9 10 template<class T> 11 class intrusive_ptr_base { 12 public: 13 /** 14 * 缺省构造函数 15 */ 16 intrusive_ptr_base(): ref_count(0) { 17 std::cout << " Default constructor " << std::endl; 18 } 19 20 /** 21 * 不允许拷贝构造,只能使用intrusive_ptr来构造另一个intrusive_ptr 22 */ 23 intrusive_ptr_base(intrusive_ptr_base<T> const&): ref_count(0) { 24 std::cout << " Copy constructor..." << std::endl; 25 } 26 27 /** 28 * 不允许进行赋值操作 29 */ 30 intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) { 31 std::cout << " Assignment operator..." << std::endl; 32 return *this; 33 } 34 35 /** 36 * 递增引用计数(放到基类中以便compiler能找到,否则需要放到boost名字空间中) 37 */ 38 friend void intrusive_ptr_add_ref(intrusive_ptr_base<T> const* s) { 39 std::cout << " intrusive_ptr_add_ref..." << std::endl; 40 assert(s->ref_count >= 0); 41 assert(s != 0); 42 ++s->ref_count; 43 } 44 45 /** 46 * 递减引用计数 47 */ 48 friend void intrusive_ptr_release(intrusive_ptr_base<T> const* s) { 49 std::cout << " intrusive_ptr_release..." << std::endl; 50 assert(s->ref_count > 0); 51 assert(s != 0); 52 if (--s->ref_count == 0) 53 boost::checked_delete(static_cast<T const*>(s)); //s的实际类型就是T,intrusive_ptr_base<T>为基类 54 } 55 56 /** 57 * 类似于shared_from_this()函数 58 */ 59 boost::intrusive_ptr<T> self() { 60 return boost::intrusive_ptr<T>((T*)this); 61 } 62 63 boost::intrusive_ptr<const T> self() const { 64 return boost::intrusive_ptr<const T>((T const*)this); 65 } 66 67 int refcount() const { 68 return ref_count; 69 } 70 71 private: 72 ///should be modifiable even from const intrusive_ptr objects 73 mutable boost::detail::atomic_count ref_count; 74 75 };
用户类类型需要继承intrusive_ptr_base基类,以便具有引用计数功能。
1 #include <iostream> 2 #include <string> 3 #include <boost/intrusive_ptr.hpp> 4 #include "intrusive_ptr_base.hpp" 5 6 /** 7 * 用户类类型继承自intrusive_ptr_base,该实现方式类似于boost::enable_shared_from_this<Y> 8 */ 9 class Connection : public intrusive_ptr_base< Connection > { 10 public: 11 /** 12 * 构造函数,调用intrusive_ptr_base< Connection >的缺省构造函数来初始化对象的基类部分 13 */ 14 Connection(int id, std::string tag): 15 connection_id( id ), connection_tag( tag ) {} 16 17 /** 18 * 拷贝构造函数,只复制自身数据,不能复制引用计数部分 19 */ 20 Connection(const Connection& rhs): 21 connection_id( rhs.connection_id ), connection_tag( rhs.connection_tag) {} 22 23 /** 24 * 赋值操作,同样不能复制引用计数部分 25 */ 26 const Connection operator=( const Connection& rhs) { 27 if (this != &rhs) { 28 connection_id = rhs.connection_id; 29 connection_tag = rhs.connection_tag; 30 } 31 32 return *this; 33 } 34 35 private: 36 int connection_id; 37 std::string connection_tag; 38 }; 39 40 int main() { 41 std::cout << "Create an intrusive ptr" << std::endl; 42 boost::intrusive_ptr< Connection > con0 (new Connection(4, "sss") ); //调用intrusive_ptr_add_ref()递增引用计数 43 std::cout << "Create an intrusive ptr. Refcount = " << con0->refcount() << std::endl; 44 45 boost::intrusive_ptr< Connection > con1 (con0); //调用intrusive_ptr_add_ref() 46 std::cout << "Create an intrusive ptr. Refcount = " << con1->refcount() << std::endl; 47 boost::intrusive_ptr< Connection > con2 = con0; //调用intrusive_ptr_add_ref() 48 std::cout << "Create an intrusive ptr. Refcount = " << con2->refcount() << std::endl; 49 50 std::cout << "Destroy an intrusive ptr" << std::endl; 51 52 return 0; 53 }
程序运行输出:
Create an intrusive ptr
Default constructor
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 1
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 2
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 3
Destroy an intrusive ptr
intrusive_ptr_release...
intrusive_ptr_release...
intrusive_ptr_release...
4.4 boost::intrusive_ptr与boost::shared_ptr区别
使用boost::shared_ptr用户类本身不需要具有引用计数功能,而是由boost::shared_ptr来提供;使用boost::shared_ptr的一大陷阱就是用一个raw pointer多次创建boost::shared_ptr,这将导致boost::shared_ptr析构时该raw pointer被多次销毁当。即不能如下使用:
1 int *a = new int(5); 2 boost::shared_ptr ptr1(a); 3 boost::shared_ptr ptr2(a); //错误!
boost::intrusive_ptr完全具备boost::shared_ptr的功能,且不存在shared_ptr的问题,即可以利用raw pointer创建多个intrusive _ptr,其原因就在于引用计数的ref_count对象,shared_ptr是放在shared_ptr结构里,而目标对象T通过继承intrusive_ptr_base将引用计数作为T对象的内部成员变量,就不会出现同一个对象有两个引用计数器的情况出现。
那么为什么通常鼓励大家使用shared_ptr,而不是intrusive_ptr呢, 在于shared_ptr不是侵入性的,可以指向任意类型的对象; 而intrusive_ptr所要指向的对象,需要继承intrusive_ptr_base,即使不需要,引用计数成员也会被创建。
4.5 结论
如果创建新类且需要进行传递,则继承intrusive_ptr_base,使用intrusive_ptr。
5. Boost::weak_ptr<T>
5.1 定义
weak_ptr 就是一个弱指针。weak_ptr 被shared_ptr控制, 它可以通过share_ptr的构造函数或者lock成员函数转化为share_ptr。
5.2 特点
(1)weak_ptr的一个最大特点就是它共享一个share_ptr的内存
(2)无论是构造还是析构一个weak_ptr都不会影响引用计数器
5.3 弱引用与强引用
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。
1 boost::weak_ptr 2 3 boost::weak_ptr<T>是boost提供的一个弱引用的智能指针,它的声明可以简化如下: 4 5 namespace boost { 6 7 template<typename T> class weak_ptr { 8 public: 9 template <typename Y> 10 weak_ptr(const shared_ptr<Y>& r); 11 12 weak_ptr(const weak_ptr& r); 13 14 ~weak_ptr(); 15 16 T* get() const; 17 bool expired() const; 18 shared_ptr<T> lock() const; 19 }; 20 }
可以看到,boost::weak_ptr必须从一个boost::share_ptr或另一个boost::weak_ptr转换而来,这也说明,进行该对象的内存管理的是那个强引用的boost::share_ptr。boost::weak_ptr只是提供了对管理对象的一个访问手段。
boost::weak_ptr除了对所管理对象的基本访问功能(通过get()函数)外,还有两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用指针。
5.4 循环引用
5.4.1 循环定义
引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象。
5.4.2 循环引用例子
1 #include <string> 2 #include <iostream> 3 #include <boost/shared_ptr.hpp> 4 #include <boost/weak_ptr.hpp> 5 6 class parent; 7 class children; 8 9 typedef boost::shared_ptr<parent> parent_ptr; 10 typedef boost::shared_ptr<children> children_ptr; 11 12 class parent 13 { 14 public: 15 ~parent() { std::cout <<"destroying parent "; } 16 17 public: 18 children_ptr children; 19 }; 20 21 class children 22 { 23 public: 24 ~children() { std::cout <<"destroying children "; } 25 26 public: 27 parent_ptr parent; 28 }; 29 30 31 void test() 32 { 33 parent_ptr father(new parent()); 34 children_ptr son(new children); 35 36 father->children = son; 37 son->parent = father; 38 } 39 40 void main() 41 { 42 std::cout<<"begin test... "; 43 test(); 44 std::cout<<"end test. "; 45 }
运行该程序可以看到,即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的内存泄漏。
一般来讲,解除这种循环引用有下面有三种可行的方法:
(1)当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
(2)当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent。
(3)使用弱引用的智能指针打破这种循环引用。
虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。这里主要介绍一下第三种方法和boost中的弱引用的智能指针boost::weak_ptr。
5.4.3 通过boost::weak_ptr来打破循环引用
由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。对于上面的那个例子来说,只要把children的定义改为如下方式,即可解除循环引用:
1 class children 2 { 3 public: 4 ~children() { std::cout <<"destroying children "; } 5 6 public: 7 boost::weak_ptr<parent> parent; 8 };
最后值得一提的是,虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。毕竟,对于C++来说,由于没有垃圾回收机制,内存泄漏对每一个程序员来说都是一个非常头痛的问题。
5.5 使用原则
(1)要打破递归的依赖关系
(2)使用一个共享的资源而不需要共享所有权
(3)避免悬空的指针
(4)shared_ptr构造weak_ptr时,weak_ptr所指内存为空会抛出异常,而weak_ptr的lock()成员不会抛出异常但会返回个空指针,根据自己需求选择
5.6 例子
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 boost::shared_ptr<test> sharePtr(new test);; 4 boost::weak_ptr<test> weakPtr(sharePtr); 5 //weakPtr 就是用來保存指向這塊內存區域的指針的 6 //干了一大堆其他事情 7 boost::shared_ptr<test> sharePtr_2 = weakPtr.lock(); 8 if (sharePtr_2) 9 sharePtr_2->print(); 10 return 0; 11 }
6. Boost::shared_array<T> 和Boost::scoped_array<T>
6.1 定义
前面提到过shared_ptr和scoped_ptr不能用于数组的内存(new []),所以shared_array和scoped_array就是他们的代替品。
6.2 例子
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 const int size = 10; 4 boost::shared_array<test> a(new test[]); 5 for (int i = 0; i < size; ++i) 6 a[i].print(); 7 return 0; 8 }
7.std::auto_ptr
7.1 定义
auto_ptr是C++标准库里的类,它接受一个类型形参的模板,为动态分配的对象提供异常安全。其实,它的核心思想是:用一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。
7.2 特点
(1)auto_ptr的构造函数带explicit 关键字,必须使用初始化的直接形式来创建auto_ptr对象。
1 auto_ptr<int> ap(new int(1024)); //ok 2 auto_ptr<int> ap=new int(1024); //error
(2)auto_ptr 在析构函数中释放了动态分配的空间,因此能自动释放内存。下面函数只动态分配了内存,并没有显示释放。但是编译器保证在展开栈越过f之前运行pi的析构函数。
1 void f() { auto_ptr<int> ap(new int(1024)); }
(3)auto_ptr重载了解引用操作符和箭头操作符,支持了普通指针的行为。
(4)赋值时删除了左操作数指向的对象
1 auto_ptr<int> ap1(new int(1024)); auto_ptr<int> ap2; 2 ap2=ap1;
将ap1赋值给ap2后,删除了ap2原来指的对象;ap2置为指向ap1所指的对象;ap1为未绑定对象。可看代码。
(5)测试auto_ptr对象,可以调用get成员函数,该函数返回包含在auto_ptr对象中的基础指针。
1 if(ap.get()) *ap=512; //ok 2 if(ap) *ap=512; //error
7.3 使用原则
尽管auto_ptr类模板为处理动态分配的内存提供了安全性和便利性的尺度,但是也存在不少缺陷,接下来结合例子给出auto_ptr的一些缺陷。
(1)不要使用auto_ptr对象保存指向静态分配对象的指针。否则,当auto_ptr对象本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
1 int a=1; 2 auto_ptr<int> ap(&a); //编译没有问题,会导致未定义行为
(2)不要使两个auto_ptr对象指向同一对象。
1 auto_ptr<int> ap1(new int (1024)); 2 auto_ptr<int> ap2(ap1.get());
(3)不要使用auto_ptr对象保存指向动态分配数组的指针。从源代码中可以看出,它用的是delete操作符,而不是delete [ ] 操作符
(4)不要将auto_ptr对象存储在容器中。因为auto_ptr的复制和赋值具有破坏性。不满足容器要求:复制或赋值后,两个对象必须具有相同值。
7.4 例子
1 #include <utility> 2 #include <iostream> 3 using namespace std; 4 5 class A 6 { 7 public: 8 A() { id = ++count; cout << "create A" << id << " "; } 9 ~A() { cout << "destroy A" << id << " "; } 10 private: 11 static int count; 12 int id; 13 }; 14 15 int A::count = 0; 16 17 /*调用该函数会丢失掉所有权*/ 18 void sink(auto_ptr<A> a) 19 { 20 cout << "Enter sink() "; 21 } 22 23 /*调用该函数会创建对象,并获取所有权*/ 24 auto_ptr<A> create() 25 { 26 cout << "Enter create() "; 27 auto_ptr<A> a(new A()); 28 return a; 29 } 30 31 int main(int argc, char *argv[]) 32 { 33 auto_ptr<A> a1 = create(); 34 auto_ptr<A> a2 = a1; /*转移所有权,此时a1无效了*/ 35 auto_ptr<A> a3(new A()); 36 cout << "Exit create() "; 37 sink(a2);/*丢失所有权,会发现a2的释放在sink函数中进行*/ 38 cout << "Exit sink() "; 39 return 0; 40 } 41 42 输出结果是:<br>Enter create()<br>create A1<br>create A2<br>Exit create()<br>Enter sink()<br>destroy A1<br>Exit sink()<br>destroy A2<br><br>
8. 使用智能指针的几个注意点
(1)声明一个智能指针的时候要立即给它实例化, 而且一定不能手动释放它。
(2)…_ptr<T> 不是T* 类型。所以:
a: 声明的时候要…_ptr<T> 而不是….._ptr<T*>。
b:不能把T* 型的指针赋值给它。
c: 不能写ptr=NULL, 而用ptr.reset()代替。
(3)不能循环引用。
(4)不要声明临时的share_ptr, 然后把这个指针传递给一个函数。
原文链接:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html