您的位置:首页 > 编程语言 > C语言/C++

C++智能指针详解

2016-05-13 14:46 162 查看
本文转载自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

std::auto_ptr 属于STL,当然在namespace std中,包含头文件 #include<memory>便可以使用。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();
}
}



看到什么异常了吗?我们创建出来的对象没有被析构,没有输出~Simple: 1,导致内存泄露。正确的写法应该是下面这两种。
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函数,因为可以用智能指针去管理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: