智能指针是行为类似于指针的类对象。模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案,unique_ptr和shared_ptr。
1. 使用智能指针
这三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。常规指针与auto_ptr的区别,如下图所示:
//创建int类型的智能指针
#include <memory>
std::auto_ptr<int> pi(new int);//替代int* p = new int;
std::unique_ptr<int> piu(new int);
std::shared_ptr<int> pis(new int);
double *p = new double;
std::shared_ptr<double> pb(p);
std::shared_ptr<double> pb2 =shared_ptr<double>(p);
std::shared_ptr<double> pb3 = p; //x 隐式转换,不允许
所有智能指针类都一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转换为智能指针对象。
对全部三种智能指针都应避免的一点:
pvac过期时,程序将把delete运算符用于非堆内存,这是错误的。
智能指针对象的很多方面都类似于常规指针。例如,如果ps是一个智能指针对象,则可以对它执行解除引用操作(* ps)、用它来访问结构成员(ps->puffIndex)、将它赋给指向相同类型的常规指针。还可以将智能指针对象赋给另一个同类型的智能指针对象,但将引起一个问题。
2.有关智能指针的注意事项
为何C++11摒弃auto_ptr呢?先来看下面的赋值语句:
如果ps和vocation是常规指针,则两个指针将指向同一个string对象。这是不能接受的,因为程序将试图删除同一个对象两次——一次是ps过期时,另一次是vocation过期时。要避免这种问题,方法有多种:
- 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。
- 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格。
- 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference counting)。例如,赋值时,计数将加1,而指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。
下例是不适合使用auto_ptr的示例。
#include<iostream>
#include<string>
#include<memory>
int main()
{
using namespace std;
auto_ptr<string> films[5] =
{
auto_ptr<string> (new string("Fowl Balls")),
auto_ptr<string> (new string("Duck Walk")),
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]失去了所有权
for(int i =0;i<5;i++)
cout<<*films[i]<<endl;
return 0;
}
程序输出如下:
这里的问题在于,下面的语句将所有权从films[2]转让给pwin:
pwin = films[2];
这导致films[2]不再引用该字符串。在auto_ptr放弃对象的所有权后,便可能使用它来访问该对象。当程序打印films[2]指向的字符串时,却发现这是一个空指针。使用shared_ptr代替auto_ptr(这要求编译器支持C++11新增的shared_ptr类),则程序将正常运行。若使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误:
pwin = films[2];
因此,unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。
有时候,将一个智能指针赋给另一个并不会留下危险的悬挂指针。程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做:
语句#1将留下悬挂的unique_ptr(pul),这可能导致危害。语句#2不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给pu后就会被销毁。
要安全地重用这种指针,可给它赋新值。C++有一个标准库函数std::move( ),让您能够将一个unique_ptr赋给另一个。
模板auto_ptr使用delete而不是delete [ ],因此只能与new一起使用,而不能与new [ ]一起使用。但unique_ptr有使用new [ ]和delete [ ]的版本:
注:
使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new [ ]分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr或shared_ptr;不使用new或new []分配内存时,不能使用unique_ptr。
3.选择智能指针
如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;两个对象包含都指向第三个对象的指针;STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。
如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。
在unique_ptr为右值时,可将其赋给shared_ptr。
在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。
4.补充weak_ptr
weak_ptr可以指向shared_ptr指针指向的对象内存,却并不用用该内存。使用weak_ptr成员lock可以返回其指向内存的shared_ptr对象,且在所指对象内存已经无效时返回nullptr。
#include<iostream>
#include <memory>
using namespace std;
//enum class type{a,b};
void CheckWp(weak_ptr<int> &wp)
{
shared_ptr<int> sp = wp.lock();//转换为shared_ptr
if(sp == nullptr)
cout<<"point is invalid"<<endl;
else
cout<<"still "<<*sp<<endl;
return;
}
int main()
{
shared_ptr<int> sp1 = make_shared<int>(22);
shared_ptr<int> sp2 = sp1;
weak_ptr<int> wp = sp1;//指向shared_ptr<int> 所指对象
cout<<*sp1<<endl;//22
cout<<*sp2<<endl;//22
CheckWp(wp);//still 22
sp1.reset();
cout<<*sp2<<endl;//22
CheckWp(wp);//still 22
sp2.reset();
CheckWp(wp);//point is invalid
return 0;
}