条款13:以对象管理资源
许多资源被动态分配于heap内而后被用于单一区块或函数内。它们应该在控制流离开那个区块或函数时被释放。标准程序库提供的auto_ptr正是针对这种形式而设计的特制产品。auto_ptr是个"类指针(pointer-like)对象",也就是所谓"智能指针",其析构函数自动对其所指对象调用delete。
以对象管理资源的两个关键想法:
1. 为防止资源泄露,请使用RAII对象(RAII:资源取得时机便是初始化时机),它们在构造函数中获得资源并在析构函数中释放资源。()
2. 两个常被使用的RAII classes分别是shared_ptr和auto_ptr。前者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作(通过copy构造函数或copy assignment操作符)会使它(被复制物)指向null。(auto_ptr和shared_ptr两者都在其析构函数内做delete而不是delete[]动作。那意味在动态分配而得的array身上使用auto_ptr或shared_ptr是个馊主意。尽管如此那么做仍能通过编译。注意并没有特别针对"C++"动态分配数组“而设计的类似auto_ptr或shared_ptr那样的东西。那是因为vector和string总是可以取代动态分配而得的数组。)
如果你打算手工释放资源(例如使用delete而非使用一个资源管理类;resource-managing class),容易发生某些错误。罐装式的资源管理类如auto-ptr和shared_ptr往往比较能够轻松遵循本条款忠告。
条款14:在资源管理类中小心copying行为
由于并非所有资源都是heap-based,对这种资源而言,像auto_ptr和shared_ptr这样的智能指针往往不适合作为资源掌管者。既然如此,有可能偶而你会发现需要建立自己的资源管理类.但是当一个RAII对象被复制(也即是当资源管理类被复制时),会发生什么事?
1. 复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
2. 普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。(Copying函数(包括copy构造函数和copy assignment操作符)有可能被编译器自动创建出来,因此除非编译器所生版本做了你想要做的事,否则你得自己编写它们。)
条款15:在资源管理类中提供对原始资源的访问
资源管理类(resource-managing classes)很棒。在一个完美世界中你将依赖这样的classes来处理和资源之间的所有互动,而不是玷污双手直接处理原始资源(raw resources)。但这个世界并不完美。许多APIs直接置涉资源,所以不得不绕过资源管理对象(resource-managing objects)直接访问原始资源(raw resources).1. APIs往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个"取得其所管理之资源"的办法。
2. 对原始资源的访问可能经由显示转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。
条款16:成对使用new和delete时要采取相同形式
当你使用new(也就是通过new动态生成一个对象),有两件事发生。第一,内存被分配出来(通过名为operator new的函数,见条款49和条款51)。第二,针对此内存会有一个(或更多)构造函数被调用。当你使用delete,也有两件事发生:针对此内存会有一个(或更多)析构函数被调用,然后内存才被释放(通过名为operator delete的函数,见条款51)。delete的最大问题在于:即将被删除的内存之内究竟有多少对象?这个问题的答案决定了有多少个析构函数必须被调用起来。
当你对着一个指针使用delete,唯一能够让delete知道内存中是否存在一个"数组大小记录"的办法就是:由你来告诉它。如果你使用delete时加上中括号(方括号),delete便认定指针指向一个数组,否则它便认定指针指向单一对象。
最好尽量不要对数组形式做typedefs动作,因为当程序员以new创建该种typedef类型对象时,不一定总记得是以delete还是delete [ ] 来调用。
1. 如果你在new表达式中使用[],必须在相应的delete表达式中也使用[]。如果你在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。
条款17:以独立语句将newed对象置入智能指针
像类似processWidget(std::shared_ptr<Widget>(new Widget),priority())的函数中:调用priority,执行"new Widget",调用shared_ptr构造函数三件事以什么样的次序完成弹性很大。这和其他语言如Java和C#不同,那两种语言总是以特定次序完成该函数参数的核算。如果编译器选择先new Widget,调用priority,调用shared_ptr构造函数这样的次序,但是万一对priority的调用导致异常,会发生什么事,此种情况下"new Widget"返回的指针将会遗失,因为它尚未被置入shared_ptr内,后者是我们期盼用来防卫资源泄漏的武器。这样在对processWidget的调用过程中可能引发资源泄漏,因为在"资源被创建(经由'new Widget')"和"资源被转换为资源管理对象"两个时间点之间有可能发生异常干扰。避免这类问题的办法很简单:使用分离语句,分别写出创建Widget将它置入一个智能指针,然后再把那个智能指针传给processWidget;
1. 以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄露。