本文转载自C++智能指针详解
一、简介
由于C++语言没有自动内存回收机制,程序员每次new出来的内存都要手动delete。程序员忘记delete,流程太复杂最终导致没有delete,异常导致程序过早退出等等没有执行delete的情况并不罕见。用智能指针便可以有效解决这类问题,本文主要讲解智能指针的用法。包括auto_ptr、scoped_ptr、shared_ptr、scoped_array、shared_array、weak_ptr、intrusive_ptr。你可能会想,如此多的智能指针就为了解决new、delete匹配问题,真的有必要吗?看完这篇文章后,我想你心里自然会有答案。下面就按照顺序讲解如上7种智能指针(smart_ptr)。
二、具体使用
1、总括
对于编译器来说,智能指针实际上是一个栈对象并非指针类型。在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。所有智能指针都重载了operator->操作符直接返回对象的引用用以操作对象,访问智能指针原来的方法则使用.操作符。访问智能指针包含的裸指针则可以用get()函数。由于智能指针是一个对象,所以if(my_smart_object)永远为真。要判断智能指针的裸指针是否为空,需要这样判断:if(my_smart_object.get())。智能指针包含了reset()方法,如果不传递参数或者传递NULL,则智能指针会释放当前管理的内存;如果传递一个对象,则智能指针会释放当前对象来管理新传入的对象。我们编写一个测试类来辅助分析:
class Simple { public: Simple(int param=0) { number=param; cout<<"Simple: "<<number<<endl; } ~Simple() { cout<<"~Simple: "<<number<<endl; } void PrintSomething() { cout<<"PrintSomething: "<<info_extend.c_str()<<endl; //c_str()生成一个const char*指针指向以空字符终止的数组 } string info_extend; int number; };2、std::auto_ptr
void TestAutoPtr() { auto_ptr<Simple> my_memory(new Simple(1)); if(my_memory.get()) { my_memory->PrintSomething(); my_memory.get()->info_extend="Addition"; my_memory->PrintSomething(); (*my_memory).info_extend+=" other"; my_memory->PrintSomething(); } }
上述为正常使用std::auto_ptr的代码,一切似乎都正常。但是好景不长,我们看看另一个例子。
void TestAutoPtr2() { auto_ptr<Simple> my_memory(new Simple(1)); if(my_memory.get()) { auto_ptr<Simple> my_memory2; my_memory2=my_memory; my_memory2->PrintSomething(); my_memory->PrintSomething(); } }如上代码导致崩溃,跟进std::auto_ptr的源码后我们看到罪魁祸首是my_memory2=my_memory这行代码,my_memory2完全夺取了my_memory的内存管理所有权,导致 my_memory悬空,最后使用时导致崩溃。所以使用std::auto_ptr时绝对不能使用operator=操作符。作为一个库不允许用户使用却没有明确拒绝,多少会觉得有点出乎预料。我们再看一个例子。
void TestAutoPtr3() { auto_ptr<Simple> my_memory(new Simple(1)); if(my_memory.get()) { my_memory.release(); } }
void TestAutoPtr4() { auto_ptr<Simple> my_memory(new Simple(1)); if(my_memory.get()) { my_memory.reset(); } }
void TestAutoPtr5() { auto_ptr<Simple> my_memory(new Simple(1)); if(my_memory.get()) { Simple* temp_memory=my_memory.release(); delete temp_memory; } }
std::auto_ptr的release()函数只是让出内存所有权,这显然不符合C++编程思想。std::auto_ptr可用来管理单个对象的堆内存,但是请注意如下几点:
(1)std::auto_ptr最好不要当成参数传递。
(2)记住release()函数不会释放对象,仅仅归还所有权。
(3)尽量不要使用operator=。如果使用了,请不要再使用先前对象。
(4)由于std::auto_ptr的operator=问题,有其管理的对象不能放入std::vector等容器中。
(5)……
使用一个std::auto_ptr的限制还真多,还不能用来管理堆内存数组,这应该是你目前在想的事情吧,我也觉得限制挺多的,哪天一个不小心,就导致问题了。由于 std::auto_ptr 引发了诸多问题,所以引发了下面boost的智能指针,boost智能指针可以解决如上问题。
3、boost::scoped_ptr
接下来我们要讲得这些智能指针都属于boost库,定义在namespace boost中,包含头文件#include<boost/smart_ptr.hpp>便可以使用。如何在VS种安装和使用这个第三方库可以参考百度经验怎样在VS2013中安装配置boost_1_55_0库,写的很详细。最新的VS2015和boosts1.60.0的安装步骤和这篇文章是完全一样的。boost::scoped_ptr跟std::auto_ptr一样,可以方便的管理单个堆内存对象,特别的是boost::scoped_ptr 独享所有权,避免了std::auto_ptr恼人的几个问题。
我们可以看到,boost::scoped_ptr也可以像auto_ptr一样正常使用。但其没有release()函数,不会导致先前的内存泄露问题。由于boost::scoped_ptr是独享所有权的,所以明确拒绝用户写my_memory2 = my_memory之类的语句。但是当我们需要复制智能指针时,boost::scoped_ptr满足不了我们的需求。如此我们再引入一个智能指针,专门用于处理复制参数传递的情况,这便是如下的 boost::shared_ptr。
4、boost::shared_ptr
在上面我们看到boost::scoped_ptr独享所有权,不允许赋值、拷贝。boost::shared_ptr是专门用于共享所有权的,其在内部使用了引用计数。boost::shared_ptr也是用于管理单个堆内存对象的。
void TestSharedPtr(boost::shared_ptr<Simple> memory) { memory->PrintSomething(); cout<<"TestSharedPtr UseCount: "<<memory.use_count()<<endl; } void TestSharedPtr2() { boost::shared_ptr<Simple> my_memory(new Simple(1)); if(my_memory.get()) { my_memory->PrintSomething(); my_memory.get()->info_extend="Addition"; my_memory->PrintSomething(); (*my_memory).info_extend+=" other"; my_memory->PrintSomething(); } cout<<"TestSharedPtr2 UseCount: "<<my_memory.use_count()<<endl; TestSharedPtr(my_memory); cout<<"TestSharedPtr2 UseCount: "<<my_memory.use_count()<<endl; }
boost::shared_ptr可以很方便的使用,并且没有release()函数。关键的一点,boost::shared_ptr内部维护了一个引用计数,由此可以支持复制、参数传递等。boost::shared_ptr提供了一个函数use_count(),此函数返回boost::shared_ptr内部的引用计数。查看执行结果,我们可以看到在TestSharedPtr2函数中引用计数为1;传递参数后(此处进行了一次复制)在函数TestSharedPtr内部引用计数为2;在TestSharedPtr返回后,引用计数又变为1。当我们需要使用一个共享对象的时候,boost::shared_ptr是再好不过的了。
至此我们已经看完单个对象的智能指针管理,接下来讲于智能指针管理数组。
5、boost::scoped_array
boost::scoped_array 是用于管理动态数组的。跟 boost::scoped_ptr 一样独享所有权。
boost::scoped_array的使用跟boost::scoped_ptr差不多,不支持复制并且初始化的时候需要使用动态数组。另外boost::scoped_array没有重载operator*,其实这并无大碍,一般情况下使用get()函数更明确些。下面肯定应该讲boost::shared_array了,一个用引用计数解决复制、参数传递的智能指针类。
6、boost::shared_array
由于boost::scoped_array独享所有权,显然在很多情况下不能满足需求,由此我们引入boost::shared_array。跟boost::shared_ptr 一样内部使用了引用计数。
void TestSharedArray(boost::shared_array<Simple> memory) { cout<<"TestSharedArray UseCount: "<<memory.use_count()<<endl; } void TestSharedArray2() { boost::shared_array<Simple> my_memory(new Simple[2]); if (my_memory.get()) { my_memory[0].PrintSomething(); my_memory.get()[0].info_extend="Addition 00"; my_memory[0].PrintSomething(); my_memory[1].PrintSomething(); my_memory.get()[1].info_extend="Addition 11"; my_memory[1].PrintSomething(); } cout<<"TestSharedArray2 UseCount: "<<my_memory.use_count()<<endl; TestSharedArray(my_memory); cout<<"TestSharedArray2 UseCount: "<<my_memory.use_count()<<endl; }
boost::shared_array跟boost::shared_ptr一样使用了引用计数,可以复制,通过参数来传递。我们讲过的智能指针有std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array。这几个智能指针已经基本够我们使用了,90% 的使用过标准智能指针的代码就这5种。可如下还有两种智能指针,它们肯定有用,但有什么用处呢,一起看看吧。
7、boost::weak_ptr
似乎 boost::scoped_ptr、boost::shared_ptr这两个智能指针就可以解决所有单个对象内存的管理了,这儿还多出一个boost::weak_ptr,是否还有某些情况我们没纳入考虑呢?其实boost::weak_ptr是专门为boost::shared_ptr而准备的。有时候,我们只关心能否使用对象,并不关心内部的引用计数。boost::weak_ptr是boost::shared_ptr的观察者对象,意味着boost::weak_ptr只对boost::shared_ptr进行引用,而不改变其引用计数。当被观察的boost::shared_ptr失效后,相应的boost::weak_ptr也相应失效。
void TestWeakPtr() { boost::weak_ptr<Simple> my_memory_weak; boost::shared_ptr<Simple> my_memory(new Simple(1)); cout<<"TestWeakPtr boost::shared_ptr UseCount: "<<my_memory.use_count()<<endl; my_memory_weak = my_memory; cout<<"TestWeakPtr boost::shared_ptr UseCount: "<<my_memory.use_count()<<endl; }
尽管被赋值了,内部的引用计数并没有什么变化。当然,读者也可以试试传递参数等其他情况。现在要说的问题是,boost::weak_ptr到底有什么作用呢?从上面那个例子看来,似乎没有任何作用。其实boost::weak_ptr主要用在软件架构设计中,可以在基类(此处的基类并非抽象基类,而是指继承于抽象基类的虚基类)中定义一个 boost::weak_ptr用于指向子类的boost::shared_ptr,这样基类仅仅观察自己的boost::weak_ptr是否为空就知道子类有没对自己赋值了,而不用影响子类boost::shared_ptr的引用计数,用以降低复杂度,更好的管理对象。
8、boost::intrusive_ptr
讲完如上 6 种智能指针后,对于一般程序来说C++堆内存管理就够用了,现在有多了一种boost::intrusive_ptr,这是一种插入式的智能指针,内部不含有引用计数,需要程序员自己加入引用计数。个人感觉这个智能指针没太大用处,至少我没用过。有兴趣的朋友自己研究一下源代码。
三、总结
如上讲了这么多智能指针,有必要对这些智能指针做个总结:
1、在可以使用boost库的场合下,拒绝使用std::auto_ptr,因为其不仅不符合C++编程思想,而且极容易出错。
2、在确定对象无需共享的情况下,使用boost::scoped_ptr(当然动态数组使用boost::scoped_array)。
3、在对象需要共享的情况下,使用 boost::shared_ptr(当然动态数组使用boost::shared_array)。
4、在需要访问boost::shared_ptr对象而又不想改变其引用计数的情况下,使用boost::weak_ptr,一般常用于软件框架设计中。
5、最后一点,也是要求最苛刻一点:在你的代码中,不要出现delete关键字或free函数,因为可以用智能指针去管理。