最常用的资源就是,new出来的内存,即动态分配内存,此外还有数据库连接,网络sockets等等。重要的是,一旦使用完这些资源,必须给系统。
所谓,用对象管理资源,就是利用对象销毁时,自动调用析构函数,在析构函数内释放这些资源,从而在某种程度上达到自动释放资源的目的
RAII:Resource Acquisiton Is Inintialization.取得资源时便初始化,即获得资源的同时,就使用该资源初始化管理对象。
PS:《Effective C++》关于智能指针的建议是关于auto_ptr,和tr1::shared_ptr的,而在C++11新标准中,auto_ptr可以由unique_ptr代替, tr1::shared_ptr可以使用标准库的std::shared_ptr中。
本质问题是,当RAII被复制时,其管理的资源该怎么处理。
1.禁止复制。像是C++中的输入输出流,就是禁止复制。通过两种方法可以达到禁止复制
拷贝函数声明为private,且不需要实现
C++ 11:在拷贝函数的声明后加上“=delete”
2.对底层使用“引用计数法”。《Effective》内使用的是tr1::shared_ptr,但现在有了更好的选择std::shared_ptr
3.复制底部资源。这就需要自定义拷贝函数,达到深度拷贝的功能
4.转移底部资源的拥有权。自始至终,只能有一个对象管理类。《Effective》使用的是auto_ptr,C++ 11新标准中由unique_ptr,能比auto_ptr实现更多特性
在某些情况,必须直接访问原始资源。例如 C API 没有class的概念
1. 显式。RAII提供一个成员函数,返回原始资源。如shared_ptr和unique_ptr,都提供get()函数,返回指向资源的原始指针
2. 隐式。为RAII提供到原始资源的隐式转换,即重载operator()。
class Font{ public: ... operator FontHandle() const; private: FontHandle f; };
然而在复制Font对象时,可能就会出现问题
Font f1(getFont()); FontHandle f2 = f1;
在复制的过程中,f1被隐式转换成FontHandle,然后复制给f2。也许f2会复制一份副本,也许f2直接指向f1的资源。如果是后者,一旦f1销毁,资源被释放,f2就成为类似空悬指针。
new单一对象,使用delete销毁
new对象数组,使用delete[] 销毁对象数组
转换为代码就是
T* tPtr = new T(); shared_ptr<T> p(tPtr);
对于这样一个函数
void processWidget(shared_ptr<Widget> pw, int priority); int priority();
现在调用
processWidget(shared_ptr<Widget>(new Widget()), priority());
C++是不能确定以上两个参数的方法,是按什么顺序调用的。如果出现这样一种情况,先调用priority(),但调用的过程中出现异常,这就导致智能指针没用正确初始化。在对processWidget的调用过程中,就出现的资源泄露。
注意这样一段代码
Widget * tPtr = new Widget (); processWidget(shared_ptr<Widget>(tPtr), priority()); Widget widget = *tPtr;//tPtrz指向的内存已被释放,tPtr成为空悬指针
对于此的建议是,既然用内置指针去初始化智能指针,那就不要使用内置指针
或者,一开始就使用智能指针,内置指针仅用作初始化(临时对象)
shared_ptr<Widget> pw(new T()); processWidget(pw, priority());