智能指针是行为类似指针的类对象。
这三个智能指针模板(auto_ptr,unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种 对象。当智能指针过期后,其析构函数使用delete来释放内存。因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放内存:在智能指针过期后,这些内存将自动被释放。
要创建智能指针对象,必须包含头文件memory,该文件模板定义。然后使用通常的模板语法来实例化所需类型的指针。例如,模板auto_ptr包含如下的构造函数:
template <class X> class auto_ptr{
public:
explicit auto_ptr(X* p = 0) throw();
...
}
throw()意味着构造函数不会引发异常。
auto_ptr<double> pd(new double);//创建一个指向double类型的auto_ptr智能指针pd
auto_ptr<string> pd(new string);//创建一个指向string类型的auto_ptr智能指针ps
unique_ptr<string> pdu(new string);//创建一个指向double类型的unique_ptr智能指针pdu
shared_ptr<string> pds(new string);//创建一个指向string类型的shared_ptr智能指针pss
智能指针模板位于std名称空间中,每个智能指针都放在一个代码块内,当离开代码块时,指针将过期。
#include <iostream>
#include<string>
#include<memory>
using std::cout;
using std::cin;
using std::endl;
using std::string;
class Report
{
public:
Report(const string s) :str(s) {
cout << "Object created!\n" << endl;
}
~Report() {
cout << "Object deleted!" << endl;
}
void comment() const {
cout << str << endl;
}
private:
string str;
};
int main(void) {
{
std::auto_ptr<Report> ps(new Report("using autp_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();
}
std::shared_ptr<double> pd;
double *p_reg = new double;
//pd = p_reg;//不允许,构造函数使用了explicit,关闭了隐式构造函数
pd = std::shared_ptr<double>(p_reg);//允许,显式构造函数
//std::shared_ptr<double> pshared = p_reg;//不允许,同上,隐式构造函数被关闭
std::shared_ptr<double> pshared(p_reg);//允许,显式构造
return 0;
}
智能指针对象的很多方面都类似于常规指针,例如,ps是一个智能指针对象,则可以对它执行解引用操作(*ps)、用它来访问结构成员(ps->puffIndex)、将它赋值给相同类型的常规指针。还可以将智能指针对象赋给另一个同类型的智能指针对象(有风险)。
对三种智能指针对象来说,都应避免如下情况:
string vacation("test");
shared_ptr<string> pvac(&vacation);//错误
pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。
关于智能指针的注意事项
auto_ptr的缺陷:
auto_ptr<string> ps (new string("I reigned lonely as a cloud"));
auto<string> vocation;
vocation = ps;
如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序试图将一个对象删除两次,一次是ps过期时,一次是vocation过期时。
解决办法:
- 定义赋值运算符,使之执行深复制、深拷贝。这样两个指针将指向不同的对象,其中一个是另一个的副本。
- 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针拥有它。这样只有拥有对象的智能指针的构造函数会删除对象。然后,让赋值操作转让所有权。这就是auto_ptr和unique_ptr的策略。但unique_ptr的策略更严格。
- 创建智能更高的指针,跟踪引用特定对象的指针计数。称为引用计数。例如,赋值时计数加一,而指针过期时计数减一,仅当最后一个指针过期时,才调用delete,这就是shared_ptr的策略。
unique_ptr为何优于auto_ptr
auto_ptr<string> p1(new string("auto");//#1
auto_ptr<string> p2; //#2
p2=p1; //#3
在语句#3中,p2接管string对象的所有权,p1的所有权被剥夺,这可以防止p1和p2的析构函数试图删除同一个对象:但是如果程序随后试图使用p1,这将产生错误,因为p1不再指向有效的数据。
unique_ptr的情况:
unique_ptr<string> p1(new string("auto");//#4
unique_ptr<string> p2; //#5
p2=p1; //#6
编译器会认为#6语句非法,从而避免了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;
ps = demo("Uniquely special");
demo返回的unique_ptr在赋值给ps后,被销毁。ps接管string对象的所有权。
总之,程序试图讲一个unique_ptr赋给另一个时,如果unique_ptr是临时右值,则允许,如果源unique_ptr将存在一段时间,则编译器不允许。
unique_ptr<string> pu1(new string("hello");//#1
unique_ptr<string> pu2; //#2
pu2=pu1; //#3 不允许
unique_ptr<string> pu3; //#4
pu3 = unique_ptr<string>(new string("Hi"); //#5 允许,临时对象将会被销毁
如果非要执行#3这个语句,仅当以非智能的方式使用遗弃的智能指针(如解引用时),这种赋值才会不安全。要安全地重用这种指针。可以给它赋新值。C++有一个标准库函数std::move(),可以实现将一个unique_ptr赋值给另一个。
unique_ptr<string>ps1,ps2;//#1
ps1=demo("Uniquely special");
ps2 = std::move(ps1);
ps1=demo("TEST");
cout<<*ps2<<*ps1<<endl;
相对于auto_ptr,unique_ptr还有另外一个优点。模板auto_ptr只能使用new和delete,而unique_ptr可以使用new和new[].
使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]时,不能
unique_ptr则可以使用new和new [ ].
选择智能指针
- 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。因为它有引用计数。
- 如果不需要,就使用unique_ptr。