Discussion
Team LiB
Previous Section Next Section

Discussion

C++'s language-enforced constructor/destructor symmetry mirrors the symmetry inherent in resource acquire/release function pairs such as fopen/fclose, lock /unlock, and new/delete. This makes a stack-based (or reference-counted) object with a resource-acquiring constructor and a resource-releasing destructor an excellent tool for automating resource management and cleanup.

The automation is easy to implement, elegant, low-cost, and inherently error-safe. If you choose not to use it, you are choosing the nontrivial and attention-intensive task of pairing the calls correctly by hand, including in the presence of branched control flows and exceptions. Such C-style reliance on micromanaging resource deallocation is unacceptable when C++ provides direct automation via easy-to-use RAII.

Whenever you deal with a resource that needs paired acquire/release function calls, encapsulate that resource in an object that enforces pairing for you and performs the resource release in its destructor. For example, instead of calling a pair of OpenPort/ClosePort nonmember functions directly, consider:



class Port {


public:


  Port( const string& destination );  // call OpenPort


  ~Port();                            // call ClosePort


  // … ports can't usually be cloned, so disable copying and assignment …


};





void DoSomething() {


  Port port1( "server1:80" );


  // …


}// can't forget to close port1; it's closed automatically at the end of the scope





shared_ptr<Port> port2 = /*…*/;   // port2 is closed automatically when the


                                  // last shared_ptr referring to it goes away



You can also use libraries that implement the pattern for you (see [Alexandrescu00c]).

When implementing RAII, be conscious of copy construction and assignment (see Item 49); the compiler-generated versions probably won't be correct. If copying doesn't make sense, explicitly disable both by making them private and not defined (see Item 53). Otherwise, have the copy constructor duplicate the resource or reference-count the number of uses, and have the assignment operator do the same and ensure that it frees its originally held resource if necessary. A classic oversight is to free the old resource before the new resource is successfully duplicated (see Item 71).

Make sure that all resources are owned by objects. Prefer to hold dynamically allocated resources via smart pointers instead of raw pointers. Also, perform every explicit resource allocation (e.g., new) in its own statement that immediately gives the allocated resource to a manager object (e.g., shared_ptr); otherwise, you can leak resources because the order of evaluation of a function's parameters is undefined. (See Item 31.) For example:



void Fun( shared_ptr<Widget> sp1, shared_ptr<Widget> sp2 );


// …


Fun( shared_ptr<Widget>(new Widget), shared_ptr<Widget>(new Widget) );



Such code is unsafe. The C++ Standard gives compilers great leeway to reorder the two expressions building the function's two arguments. In particular, the compiler can interleave execution of the two expressions: Memory allocation (by calling operator new) could be done first for both objects, followed by attempts to call the two Widget constructors. That very nicely sets things up for a leak because if one of the constructor calls throws an exception, then the other object's memory will never be released! (See [Sutter02] for details.)

This subtle problem has a simple solution: Follow the advice to never allocate more than one resource in a single statement, and perform every explicit resource allocation (e.g., new) in its own code statement that immediately gives the resource to an owning object (e.g., shared_ptr). For example:



shared_ptr sp1(new Widget), sp2(new Widget);


Fun( sp1, sp2 );



See also Item 31 for other advantages to using this style.

    Team LiB
    Previous Section Next Section