这篇文章介绍下C++的智能指针,当然,可能没有你想的那么智能。
为什么需要智能指针1
void remodel(string& str)
{
string* ps = new string(str);
str = *ps;
return;
}
这里不讨论这个函数有没有意义,在这段代码中,很明显,ps
没有得到释放。函数的生命周期结束时,临时变量str
和ps
都会被销毁,但ps
分配的内存还在内存空间里。正确的写法如下:
void remodel(string& str)
{
string* ps = new string(str);
str = *ps;
delete ps;
return;
}
不需要使用delete[]
,因为是C++的内置结构。
如果ps有一个析构函数,此函数会在它过期时删除指向的内存那可太好了。但ps只是一个指针,不是对象。而智能指针的思想就是设计一个模板类,代替指针实现上述功能。
auto_ptr、unique_ptr
C++98提出了模板类auto_ptr
,但是这个方案在C++11被废弃了,废弃的原因之后我们会讨论,先介绍下这个最早的智能指针。
为了介绍auto_ptr,我们先设计一个类,当其构造函数和析构函数被调用会打印点什么。
class SmartPointer
{
public:
SmartPointer(string s);
~SmartPointer();
};
SmartPointer::SmartPointer(string s)
{
cout << "SmartPointer Created!" << endl;
}
SmartPointer::~SmartPointer()
{
cout << "SmartPointer Deleted!" << endl;
}
如下代码会泄露内存,因为SmartPointer的析构函数没有被调用。
int main()
{
string myStr = "love";
auto* ps = new SmartPointer(myStr);
}
使用智能指针,需要包含memory
头文件,其格式也和正常的指针不太一样。
auto_ptr<SmartPointer> ps(new SmartPointer("myStr"));
看打印结果,这次析构函数得到了调用。
智能指针的构造函数可以接受常规指针,但是是explicit的,也就意味着无法进行隐式转换。
auto* ps = new SmartPointer("C++");
auto_ptr<SmartPointer> spp = ps; //error
auto_ptr<SmartPointer> spp2(ps);
接下来讨论一个问题:共享,这是一个智能指针需要处理但是普通指针不需要处理的问题:
auto_ptr<SmartPointer> sps(new SmartPointer("myStr"));
auto_ptr<SmartPointer> sps2;
sps2 = sps;
按照智能指针的设计,两个指针离开生命周期都会释放对应的内存,这就意味着sps2
释放的内存是空的。(普通指针不会释放内存所以不会有这个问题,但delete
也只能写一个)
有两种办法解决这个问题:
建立所有权概念(ownership)
某些对象只允许一个智能指针拥有它,且如上的赋值操作会转让所有权。这办法应用于auto_ptr
和unique_ptr
,但后者更为严格。
我们为类增加了一个成员变量a,并赋默认值1,然后把上面的代码继续写下去:
auto_ptr<SmartPointer> sps(new SmartPointer("myStr"));
auto_ptr<SmartPointer> sps2;
sps2 = sps;
cout << sps->a << endl;
代码会引发segment fault,因为sps
已经将所有权移交给sps2
,现在的sps
是空指针。访问空指针的成员变量会出错。不过有一点,如果这里是执行一个成员函数,函数只打印一句话,就没有这个问题,不明白为什么。
unique_ptr
unique_ptr
的策略类似于auto_ptr
,不过程序会在编译的时候就报错,而不是等到运行的时候再出问题。如果你的IDE
足够智能,也会给出提示: