一,先请看为什么需要智能指针?
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()).