I l@ve RuBoard Previous Section Next Section

7.4 Local Resource Management

Consider the following function in which resources are acquired at the start of the function, some processing takes place, and then the resources are released at the end of the function. Given our discussion so far, what is wrong with this function?



extern Mutex m; 


void f() 


{ 


   // resource acquisition 


   int *p = new int; 


   m.acquire(); 





   process( p ); 





   // freeing up of resources 


   m.release(); 


   delete p; 


} 

The problem is that we cannot guarantee that the resources acquired at the start of the function are ever released. If process() or a function invoked within process() throws an exception, the two resource-freeing statements following the invocation of process() are never executed. This is not an acceptable implementation in the presence of exception handling.

One solution is to introduce a try block and associated catch clause. We'll catch all exceptions, free our resources, and then rethrow the exception:



void f() 


{ 


  try { 


      // same as above 


  } 


  catch( ... ) { 


       m.release(); 


       delete p; 


       throw; 


  } 


} 

Although this solves our problem, it is not a completely satisfactory solution. We've duplicated the code to free our resources. We've prolonged the search for a handler while we catch the exception, free our resources, and then rethrow the exception. Moreover, the code itself feels considerably more complicated. We'd prefer a less invasive, more autonomous solution. In C++, this usually means defining a class.

Bjarne Stroustrup, the inventor of C++, introduced an idiom for resource management that he describes in the phrase "resource acquisition is initialization." For a class object, initialization occurs within the class constructor. Resource acquisition is accomplished within the constructor of a class. The resource is freed within the destructor. This not only automates resource management but also simplifies our programs:



#include <memory> 


void f() 


{ 


    auto_ptr<int> p( new int ); 


    MutexLock ml( m ); 


    process( p ); 


    // destructors for p and ml 


    // are implicitly invoked here ... 


} 

p and ml are local class objects. If process() executes correctly, the associated class destructors are automatically applied to p and ml before the completion of the function. But what if an exception is thrown during the execution of process()?

All active local class objects of a function are guaranteed to have their destructors applied before termination of the function by the exception handling mechanism. In our example, the destructors for p and ml are guaranteed to be invoked whether or not an exception is thrown.

The MutexLock class might be implemented as follows: [1]

[1] This is based on the excellent article "A Case Study of C++ Design Evolution" by Douglas C. Schmidt in [LIPPMAN96b].



class MutexLock { 


public: 


    MutexLock( Mutex m ) : _lock( m ) 


         { lock.acquire(); } 





    ~MutexLock(){ lock.release(); } 


private: 


    Mutex &_lock; 


}; 

auto_ptr is a template class provided by the standard library. It automates the deletion of objects allocated through the new expression, such as p. To use it, we must include its associated header file, memory:



#include <memory> 

The auto_ptr class overloads the dereference and arrow pointer operators in the same manner as we did with our iterator class in Section 4.6. This allows us to use an auto_ptr object in the same way we would use a pointer. For example,



auto_ptr< string > aps( new string( "vermeer" )); 


string *ps = new string( "vermeer" ); 





if (( aps->size() == ps->size()) && 


    ( *aps == *ps )) 


      // equal ... 

For additional discussion of the resource acquisition is initialization idiom, see Section 14.4 of [STROUSTRUP97]. For a more detailed treatment of the auto_ptr class, look at Section 8.4.2 of [LIPPMAN98].

    I l@ve RuBoard Previous Section Next Section