智能指针是行为类似于指针的类对象,但这种对象还有其他功能,下面介绍三个可帮助管理动态内存分配的智能指针模板:
void remodel(string & str) {string * ps = new string(str);...str = *ps;return;
}
对于上面的函数,每当调用时,该函数都分配堆中的内存,但从不收回,导致内存泄漏,只需在 return
前添加 delete ps
即可释放内存。但是通常会忘记这一步,而且如果存在其他异常情况导致没有执行它,也会导致内存泄漏:
void remodel(string & str) {string * ps = new string(str);...if(weird_thing()) // 可能由于抛出异常导致未执行内存泄漏throw exception();str = *ps;delete ps;return;
}
遇到这种情况,就期望指针 ps 是一个对象,这样当它过期时就可以使用析构函数删除指向的内存,为此 C++ 提供了智能指针模板,其中 auto_ptr
是 C++ 98 提供的方案,C++ 11 已将其摒弃,并提供了另外两种:unique_ptr, shared_ptr
,它们都定义了类似指针的对象,可以将 new 获得的地址赋给它,并且当智能指针过期时,这些内存将自动被释放。
要创建智能指针对象,必须包含头文件 memory ,然后使用通常的模板语法来实例化所需类型的指针:
template class auto_ptr {
public:explicit auto_ptr(X* p = 0) throw(); // 必须包含这个构造函数...
}
auto_ptr pd(new double); // double类型的智能指针
auto_ptr pd(new string); // string类型
unique_ptr pdu(new double); // 相似的用法
shared_ptr pss(new string); // 相似的用法
从写 remodel 方法:
#include
void remodel(std::string & str) {std::auto_ptr ps(new std::string(str));...if(weird_thing()) // 可能由于抛出异常导致未执行内存泄漏throw exception();str = *ps;return;
}
注意,智能指针模板位于名称空间 std 中,下面是一个使用例子:
class Report {
private:string str;
public:Report(const string s) : str(s) {cout << "created\n";}~Report() {cout << "deleted\n";}void comment() const {cout << str << endl;}
};
int main() {{auto_ptr ps(new Report("auto_ptr"));ps->comment();}{shared_ptr ps(new Report("shared_ptr"));ps->comment();}{unique_ptr ps(new Report("unique_ptr"));ps->comment();}
}
// outputs
created
auto_ptr
deleted
created
shared_ptr
deleted
created
unique_ptr
deleted
所有智能指针都有一个 explicit 构造函数,它将指针作为参数,因此不需要自动将指针转换为智能指针对象。
需要避免下面这种情况:
string v("example");
shared_ptr p(&v);
当 p 过期时,程序将把 delete 运算符用于非堆栈内存,这是错误的。
相同智能指针之间赋值也需要注意:
auto_ptr pa(new string("example")), pb;
pb = pa;
这样做会导致两个指针指向同一个内存,然后当它们相继过期时会对其进行两次 delete ,解决方法有多种:
下面举一个不适用于 auto_ptr 的例子:
auto_ptr p1(new string("example")), p2;
p2 = p1; // p1丧失所有权
cout << *p1; // 发生错误:Segmentation fault (core dumped)
由于 p1 丧失所有权,因此变成一个空指针,此时访问 p1 发生错误;如果采用 shared_ptr 则能正常工作,因为二者都指向同一块区域;如果采用 unique_ptr 则会在编译过程中察觉错误,因此这样更安全。
再来看下一种情况:
unique_ptr demo(const string s) {unique_ptr tmp(new string(s));return tmp;
}
unique_ptr ps = demo("example");
此时由于 demo 函数返回一个临时的 unique_ptr ,然后 ps 接管了原本返回的 unique_ptr 所有的对象,而返回的 unique_ptr 被销毁,这没有问题,因为 ps 拥有了 string 对象的所有权。这样做的另一个好处是 demo 函数返回的临时 unique_ptr 很快被销毁,没有机会使用它访问无效数据,因此编译器允许这种赋值。
编译器是如何区分两种情况的呢?答案是判断右值:
unique_ptr p1(new string("example"));
unique_ptr p2;
p2 = p1; // not allow
p2 = unique_ptr(new string("example")); // allow
也就是说,如果源 unique_ptr 是一个临时右值,编译器允许赋值,反之不行。因此 unique_ptr 优于 auto_ptr ,因为后者将允许这两种赋值。如果一定要使用第一种方式赋值,可以采用 std::move()
,该函数类似于 demo ,将返回一个 unique_ptr 对象:
unique_ptr p1(new string("example"));
unique_ptr p2;
p2 = std::move(p1); // allow
unique_ptr 优于 auto_ptr 的另一个原因是 unique_ptr 可以用于数组,而 auto_ptr 不能。因此 unique_ptr 可与 new [] 配套使用,而 auto_ptr 只能与 new 配套使用。
根据不同智能指针的特点,若有多个智能指针同时指向同一个对象,则使用 shared_ptr ,否则建议使用 unique_ptr 。