一,先请看为什么需要智能指针?

void remole(std::string& str)
{
std::string * ps = new std::string(str);
// ...
str = *ps; // 这之后应该追加 delete ps;
return;
}
// 显然new了,但没有delete,会导致内存泄漏
// 所以一旦new了,别忘记在return之前加上:
// delete ps;

// 但即使没有忘记,也有可能出问题
void remole(std::string& str)
{
std::string * ps = new std::string(str);
// ...
if (weird_thing())
throw exception(); // 当出现异常时,delete将不被执行,导致内存泄漏
str = *ps;
delete ps;
return;
}

我们来看下这里的过程:

当remole()函数终止时(无论异常终止,还是正常终止),本地变量都将从栈内存中删除( 这里指ps指针所占的内存),如果当ps指针在栈中所占内存被释放时,ps指针所指向的内存也被释放,则内存泄漏的问题不就解决了么?

一个构思是:当ps有一个析构函数时,该析构函数在ps过期时,释放ps指向的内存。

这里产生了另外一个问题:

ps只是一个常规的指针,当它是一个有析构函数的类对象时才会调用析构函数,删除内存。

auto_ptr:C++98提供的解决方案,C++11已将其摒弃,提供了下面两种解决方案。

unique_ptr:

shared_ptr:


weak_ptr:第四种智能指针(这里不涉及)

上述这三种智能指针模板都定义了类似指针的对象,可以将new返回的地址赋给这种对象。

当智能指针过期时,其析构函数将使用delete来释放内存。

所以将new返回的地址赋给这些对象时,将无需记住稍后要释放这些内存。


用智能指针改写remodel()函数,则:

1,包括头文件memory

2,将指向string的指针改为指向string的智能指针对象

3,删除delete语句

具体如下:

#include <memory> //智能模板的头文件
void remodel(std::string& str)
{
std::auto_ptr<std::string> ps(new std::string(str)); // autuo_ptr的用法
// ...
if (weird_thing())
throw exception(); // 当出现异常时,delete将不被执行,导致内存泄漏
str = *ps;
// delete ps; // 不再需要
return;
}


二,关于智能指针的注意事项:

std::auto_ptr<std::string> ps (new std::string("I reigned lonely as a cloud."));
std::auto_ptr<std::string> vocation;
vocation = ps;

如果ps 与 vocation 是常规指针,则两个指针指向同一个对象,会出现程序删除同一对象两次的情况。

避免方法:

1)定义赋值运算符,使之执行深复制。这时两个指针指向不同的对象,其中一个是另一个对象的副本。

2)建立所有权概念。只能有一个智能指针拥有它,但赋值操作符会转向拥有权。auto_ptr,unique_ptr的策略,其中unique_ptr更严格。

3)使用引用计数。shared_ptr的策略。


这里说明auto_ptr的缺点:

#include <iostream>
#include <memory> //智能模板的头文件
#include <string>

int main()
{
using namespace std;
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]失去拥有权变为空指针,pwin获得拥有权

for (int i = 0; i < 5; ++i)
cout << *films[i] << endl; //这里打印到films[2]时,程序崩溃
cout << "The winner is " << *pwin << "!\n";
cin.get();
return 0;
}

如全部换成shared_ptr,则:

#include <iostream>
#include <memory> //智能模板的头文件
#include <string>

int main()
{
using namespace std;
shared_ptr<string> films[5] = {
shared_ptr<string> (new string("Fowl Balls")),
shared_ptr<string> (new string("Duck Walks")),
shared_ptr<string> (new string("Chicken Runs")),
shared_ptr<string> (new string("Turkey Errors")),
shared_ptr<string> (new string("Goose Eggs"))
}; //这上面所有的引用计数为1
shared_ptr<string> pwin;
pwin = films[2]; // //这里的引用计数为2,pwin与films[2]指向同一个对象

for (int i = 0; i < 5; ++i)
cout << *films[i] << endl; //这时能打印所有的信息
cout << "The winner is " << *pwin << "!\n";
cin.get();
return 0;
}
//当程序结束时,后声明的pwin先调用其析构函数,该析构函数将引用计数降为1,
//然后,share_ptr数组的成员被释放,对films[2]调用析构函数将引用计数降为0。
//当计数降为0,则释放以前分配的空间。


unique_ptr与auto_ptr的区别,为什么unique_ptr更安全?

先看auto_ptr:

auto_ptr<string> p1(new string("auto_ptr")); // #1
auto_ptr<string> p2; // #2
p2 = p1; // #3

这里#2时,p2 接管 string对象所有权后,p1的对象所有权被剥夺。可防止p1和p2的析构函数试图删除同一个对象。

但以后程序又试图使用p1,将导致问题,因为p1不在指向有效的数据。

再看unique_ptr:

<pre name="code" class="cpp">    unique_str<string> p3(new string("unique_ptr")); // #4
unique_str<string> p4; // #5
p4 = p3; // #6


编译器认为#6句非法,避免了p3不再指向有效数据的问题,编译阶段错误比潜在的程序崩溃更安全。

所以unique_ptr比auto_ptr更安全。

unique_ptr<string> demo(const char * s)
{
unique_ptr<string> temp(new string(s));
return temp;
}

unique_ptr<string> ps;
ps = demo("Uniquely special");//返回的temp将接管权交给ps后,很快被销毁

所以,程序试图将一个unique_ptr赋给另一个时,如果源 unique_ptr是一个临时右值,编译器将允许这么做;

如果源unique_ptr将存在一段时间,编译器将禁止这么做:如上上例中的p3,p4

但如果你不得不那样做,可以这样:

unique_str<string> p3(new string("unique_ptr")); 
unique_str<string> p4;
p4 = move(p3); // #7

这里#7使用了最新的C++11新增的移动构造函数与右值引用。

相比auto_ptr, unique_ptr还有一个优点:

它有一个可用于数组的变体。这里指:必须将new 与 delete 配对, new[ ]与delete[ ] 配对。

模板 auto_ptr使用delete,而不是delete [ ],因此只能与new一起使用,而不能与 new[ ] 一起使用。

而unique_ptr两种都能使用。

注意:

使用new分配内存时,才能使用 auto_ptr 和 shared_ptr,也可以使用unique_ptr;

在使用new[ ] 分配内存时,则不能使用 auto_ptr 或 shared_ptr,只能使用unique_ptr

在不使用new 或 new[ ]时,auto_ptr ,shared_ptr , unique_ptr 都不能使用


  三,智能指针的选择

1,如果程序要使用多个指向同一对象的指针,应选择 shared_ptr,如果编译器没有提供shared_ptr,可以使用Boost库提供的shared_ptr.

     例:1)有一个指针数组,并使用一些辅助指针来指定特定的元素,如最大的元素与最小的元素;


             3)STL容器包含指针。因为其大多支持复制与赋值操作,但其不能使用auto_ptr (行为不确定)与 unique_ptr(编译器发出警告)

2,如果程序不需要多个指向同一对象的指针,则可以使用unique_ptr,

    例:1)如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型指定为unique_ptr是个不错的选择

      2)可将unique_ptr存储在STL容器中,只要不用复制或赋值方法或算法(如sort()).