1 使用智能指针的原因
我们知道C/C++的指传统针可以用来通过指向对象地址来间接访问该对象,但是当某个指针指向人为申请的堆空间,我们时常忘记手动释放该空间从而造成内存泄漏。我们可以看看下面的代码:
void outputStr(std::string & str)
{
std::string *ps = new std::string(str);
std::cout<<*ps<<endl;
...
return;
}
上面的程序我们可以清晰的看出该函数输出结果,但是并没有手动释放堆内存所以会造成内存泄漏。所以需要在return之前加入代码delete ps;
但是有经验的工程师都知道:凡涉及“别忘了”的解决方法,很少是最佳的。
我们来看看下面的变体:
void outputStr(std::string & str)
{
std::string *ps = new std::string(str);
...
if(...)
throw exception();
delete ps;
return;
}
当异常发生时,delete不会被执行,同样会内存泄露。是时候智能指针出场了,aoto_ptr、shared_ptr、unique_ptr。 aoto_ptr是C++98中提供的,c++11已经将其摒弃了。
2 智能指针的介绍
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。
智能指针类通过显示有参构造将new获得的地址复制给智能指针。当智能指针过期时其析构函数将使用delete来释放内存(例如智能指针类在某个函数中局部构造了一个其对象,那么系统会在跳出函数时调用析构函数,而析构函数中系统有delete上面new的地址的语句)。无需记住稍后释放这些内存,在智能指针过期时,这些内存将自动被释放。
它的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。也就是说当多个智能指针对象有共享同一个指针的时候,当系统依次析构这些对象的时候,引用计数依次减一,直到引用计数变为0,也就是最后一个智能指针对象去释放该空间,前面释放的对象只起到减小引用计数的作用。
智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。当然,智能指针还不止这些,还包括复制时可以修改源对象等。智能指针根据需求不同,设计也不同(写时复制,赋值即释放对象拥有权限、引用计数等,控制权转移等)。
3 智能指针的使用
使用智能指针的步骤如下:
①. 包含对应的头文件:
#include<memory>
②. 显示构造智能指针类的对象,并复制一个堆空间给它:
aoto_ptr<double> pd(new double);
auto_ptr<string> ps(new string);
unique_ptr<double> pdu(new double);
shared_ptr<string> pss(new string);
那如何修改上面的程序,让普通指针变为智能指针来管理呢?步骤如下:
①. 包含头文件memory;
②. 将指向string的指针替换为指向string的智能指针对象;
③. 删除delete语句。
具体实现如下:
#include<memory>
void remodel(std::string & str)
{
std::auto_ptr<std::string> ps(new std::string(str));
...
if(...)
throw exception();
//delete ps;
return;
}
4 智能指针的显示构造
我们可以看一下auto_ptr如何定义的:
template<class X>
class auto_ptr
{
public:
explicit auto_ptr(X *p = 0) throw();
...
};
智能指针的构造函数使用了explicit关键字。 explicit关键字只能用于修饰只有一个参数的类的构造函数,它的作用是表面该构造函数是显示的,而非隐式的。可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。
我们看一下下面的使用示例:
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; //错误,不允许隐式转换
pd = shared_ptr<double>(p_reg);//正确
shared_ptr<double> pshared = p_reg;//错误,不允许隐式转换
shared_ptr<double> pshared(p_reg); //正确
5 切忌把智能指针用于非堆内存
我们知道由于智能指针类析构函数中有释放赋予它地址的操作,一旦我们对一段非堆内存释放将会发生系统报错。如下:
string vacation("hello world");
shared_ptr<string>pvac(&vacation);
当pvac过期时,程序将把delete运算符用于非堆内存,这将导致错误。
6 三种智能指针类的区别
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
auto_ptr<string>
films[5] = {
auto_ptr<string> (new string("Fowl Balls")),
auto_ptr<string> (new string("Duck Walks")),
auto_ptr<string> (new string("Chicken Runs")),
auto_ptr<string> (new string("Turkey Errors")),
auto_ptr<string> (new string("Goose Eggs"))
};
auto_ptr<string> pwin;
pwin = films[2]; // films[2] loses ownership. 将所有权从 films[2]转让给pwin,此时films[2]不再引用该字符串从而变成空指针
//unique_ptr<string> pwin; //编译错误
//pwin = films[2];
//shared_ptr<string> pwin; //没有问题!
cout << "The nominees for best avian baseballl film are\n";
for(int i = 0; i < 5; ++i)
cout << *films[i] << endl;
cout << "The winner is " << *pwin << endl;
return 0;
}
当使用shared_ptr的时候,pwin和films[2]指向同一个对象,而引用计数从1增加到了2。在程序末尾return后,pwin首先调用其析构函数,该析构函数将引用计数降低到1,所以不会有问题。 即 :
使用auto_ptr 所有权转让,运行将会有错误 ;
使用shared_ptr 指向同一对象,引用计数增加,不会有问题 ;
使用unique_ptr 采用所有权模型,编译就会有错误。
因此我们也可以看出unique_ptr优于auto_ptr的一个原因:
unique_ptr比auto_ptr更安全
还有一个原因就是:
auto_ptr只能与new一起使用,不能与new[]一起使用(share_ptr同样)。
unique_ptr可以使用new,也可以使用new[] 。
分析了这么多,那么归根到底,我们如何选择呢?
7 如何选择哪种智能指针
(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr 。
这样的情况包括:
①. 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
②. 两个对象包含都指向第三个对象的指针;
③. STL容器包含指针。
很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。
如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())
参考文献:
http://blog.csdn.net/wangshubo1989/article/details/48337955
http://blog.csdn.net/hackbuteer1/article/details/7561235