Tips13:以对象管理资源

一、用对象的特性来管理资源

最常用的资源就是,new出来的内存,即动态分配内存,此外还有数据库连接,网络sockets等等。重要的是,一旦使用完这些资源,必须给系统。

所谓,用对象管理资源,就是利用对象销毁时,自动调用析构函数在析构函数内释放这些资源,从而在某种程度上达到自动释放资源的目的


二、RAII对象

RAIIResource Acquisiton Is Inintialization.取得资源时便初始化,即获得资源的同时,就使用该资源初始化管理对象。


PS:《Effective C++》关于智能指针的建议是关于auto_ptr,tr1::shared_ptr的,而在C++11新标准中,auto_ptr可以由unique_ptr代替, tr1::shared_ptr可以使用标准库的std::shared_ptr中。


Tips14:在资源管理类中小心copying行为

一、当RAII(可视为资源管理类)被复制时,会发生什么?

本质问题是,当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实现更多特性


Tips15:在资源管理类中提供对原始资源的访问

一、对原始资源的访问的两种方式。

在某些情况,必须直接访问原始资源。例如 C API 没有class的概念


1. 显式。RAII提供一个成员函数,返回原始资源。如shared_ptrunique_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就成为类似空悬指针。


Tips16:成对使用newdelete时要采取相同形式

new单一对象,使用delete销毁

new对象数组,使用delete[] 销毁对象数组


Tips17:以独立语句将newed对象置入智能指针。

转换为代码就是

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());