C++ STL里面一共提供了4种智能指针:
auto_ptr<template T>, unique_ptr<template T>,
shared_ptr<template T>, auto_ptr<template T>.
第一个是C++98提供的,后面三个是C++11提供的(第一个已经被C++11摒弃)。今天重点讨论前三个,智能指针之处主要在于内存释放比较智能。
1. 为什么要引入智能指针
先看如下代码:
void remodel(std::string & str)
{
std::string * ps = new std::string(str);
...
if (weird_thing())
throw exception();
str = *ps;
delete ps;
return;
}
显然,如果异常抛出,则程序终止,内存没被释放,出现内存泄漏问题。上述代码中,ps是栈空间中的变量,异常终止后,自动释放。于是,如果我们将ps包装成一个类对象,这个类对象在栈空间中,那么一旦出现异常,ps将调用析构函数,只要在析构函数里面写上delete语句就好了。这样就可以避免因为程序员粗心导致的问题。如下所示:
# include <memory>
void remodel (std::string & str)
{
std::auto_ptr<std::string> ps (new std::string(str));
...
if (weird_thing ())
throw exception();
str = *ps;
// delete ps; NO LONGER NEEDED
return;
}
- 值得注意的是,三种只能指针都有一个explicit修饰的构造函数,也就是说必须显示初始化智能指针:
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg; // not allowed (implicit conversion)
pd = shared_ptr<double>(p_reg); // allowed (explicit conversion)
shared_ptr<double> pshared = p_reg; // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg); // allowed (explicit conversion)
- 此外,不能让智能指针指向栈空间的内存,delete栈空间的内存是不对的
string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation); // No
2. auto_ptr, unique_ptr, shared_ptr的区别
为进一步了解其内部原理,先看如下代码:
auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation;
vocaticn = ps;
ps, vocation过期时,将删除其指向的内存,可是他两指向的是同一块内存,显然,删除一个内存两次是不对的,那么其内部是怎么避免这种情况的呢?
- 定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
- 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的构造函数会删除该对象。然后让赋值操作转让所有权,即将另一个置空。这就是用于auto_ptr和unique_ptr 的策略,但unique_ptr的策略更严格,unique_ptr会在编译期间直接报错,避免这种问题(如果是不会有危险的悬挂指针就无所谓了,不会报错,必须函数返回值),但auto_ptr只是接管对象,并将另一个指针置空,但是程序员可能还会使用这个空指针,造成程序崩溃,这就是unique_ptr的高明之处。
- 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加1,而指针过期时,计数将减1,。当减为0时才调用delete。这是shared_ptr采用的策略。
3. 智能指针的选择
下面给出几个使用指南。
- 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:
1. 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
2. 两个对象包含都指向第三个对象的指针;
3. STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。 - 如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。