概述
C++98提供了了智能指针auto_ptr,但C++11已将其摒弃,并提供了unique_ptr和shared_ptr。这三种智能指针模板都定义了类似指针的对象,可以将new获得的地址赋给这种对象。当智能指针过期时,这些内存将自动被释放。其基本用法如下:
#include <iostream>
#include <string>
#include <memory>
class Report
{
private:
std::string str;
public:
Report(const std::string s) : str(s) { std::cout << "Object created!\n"; }
~Report() { std::cout << "Object deleted!\n"; }
void comment() const { std::cout << str << "\n"; }
};
int main()
{
{
std::auto_ptr<Report> ps (new Report("using auto_ptr"));
ps->comment();
}
{
std::shared_ptr<Report> ps (new Report("using shared_ptr"));
ps->comment();
}
{
std::unique_ptr<Report> ps (new Report("using unique_ptr"));
ps->comment();
}
return 0;
}
输出:
Object created!
using auto_ptr
Object deleted!
Object created!
using shared_ptr
Object deleted!
Object created!
using unique_ptr
Object deleted!
using auto_ptr
Object deleted!
Object created!
using shared_ptr
Object deleted!
Object created!
using unique_ptr
Object deleted!
我们可以将这三种智能指针分成两类。
对于auto_ptr和unique_ptr,它们使用的策略是:对于特定的对象,只能有一个智能指针可拥有它。若通过copy构造函数或copy assignment操作符复制智能指针,它们会变成null,而复制所得的指针将取得资源的唯一拥有权。这样做的好处是防止当多个auto_ptr或者unique_ptr同时指向一个对象时对象会被删除一次以上。
而对于shared_ptr,它使用的策略是”引用计数“,例如,赋值时,计数将加1,而指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。
auto_ptr和unique_ptr
C++11提倡我们使用unique_ptr,那么unique_ptr为何优于auto_ptr呢?
auto_ptr
考虑下面这段程序
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
auto_ptr<string> p1(new string("hello world")); //#1
auto_ptr<string> p2 = p1; //#2
cout << *p1 << endl; //#3
return 0;
}
运行这段程序将直接崩溃。在语句#2中,p2接管string对象的所有权后,p1的所有权将被剥夺。前面说过,这是好事,可防止p1和p2的析构函数试图删除同一个对象。但在语句#3中,我们又使用了p1,这是件坏事,因为p1不再指向有效的数据。
unique_ptr
现在我们用unique_ptr替换auto_ptr来运行程序
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
unique_ptr<string> p1(new string("hello world")); //#1
unique_ptr<string> p2 = p1; //#2
cout << *p1 << endl; //#3
return 0;
}
这次不同的是,程序在编译期间便出错了。编译器认为语句#2非法,避免了p1不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。
但有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。假设有如下函数定义:
unique_ptr<string> demo(const char *s) {
unique_ptr<string> temp(new string(s));
return temp;
}
并假设编写了如下代码:
unique_ptr<string> ps = demo("Uniquely special");
demo()返回一个临时unique_ptr,然后ps接管了原本归返回的unique_ptr所有的对象,而返回的unique_ptr被销毁。这没有问题,因为ps拥有了string对象的所有权。但这里的另一个好处是,demo()返回的临时unique_ptr很快被销毁,没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。
总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做。
然而,unique_ptr如何能够区分安全和不安全的用法呢?答案是它使用了C++11新增的移动构造函数和右值引用。
此外,unique_ptr有new[]和delete[]的版本,而auto_ptr和shared_ptr只有new和delete的版本。
shared_ptr
#include <iostream>
#include <string>
#include <memory>
using namespace std;
int main()
{
shared_ptr<string> p1(new string("hello world")); //#1
shared_ptr<string> p2 = p1; //#2
cout << *p1 << endl; //#3
return 0;
}
现在我们在程序中使用shared_ptr,程序正常运行。因为我们将p1复制给p2时,p1和p2都包含指向string对象的指针,当p1和p2都被析构时,才会调用string对象的delete函数。
删除器
shared_ptr还允许指定”删除器“,这是一个函数或函数对象,当引用次数为0时便被调用。删除器对shared_ptr构造函数而言是可有可无的第二参数。例如:当我们需要自定义一个资源管理类Lock时,对于一个Mutex的互斥器对象,在构造时调用lock(),在析构时调用unlock()。为了防止Lock对象的copying行为,我们考虑使用引用计数法,希望保存有资源直到它的最后一个使用者被销毁。此时我们可以使用shared_ptr的删除器。
class Lock {
public:
explicit Lock(Mutex *pm) : mutexPtr(pm, unlock) { //以unlock函数为删除器
lock(mutexPtr.get()); //使用get()获取原始指针
}
private:
shared_ptr<Mutex> mutexPtr;
};
智能指针的选择
如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。
如果程序不需要多个指向同一个对象的指针,则可使用unqiue_ptr。如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权将转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL窗口中,只要不调用将一个unique_ptr复制或赋给另一个的方法或算法(如sort())。