Using Constructors and Destructors to Manage Resources (or RAII)

Problem

For a class that represents some resource, you want to use its constructor to acquire it and the destructor to release it. This technique is often referred to as resource acquisition is initialization (RAII).

Solution

Allocate or acquire the resource in the constructor, and free or release the resource in the destructor. This reduces the amount of code a user of the class must write to deal with exceptions. See Example 8-3 for a simple illustration of this technique.

Example 8-3. Using constructors and destructors

#include 
#include 

using namespace std;

class Socket {
public:
 Socket(const string& hostname) {}
};

class HttpRequest {
public:
 HttpRequest (const string& hostname) :
 sock_(new Socket(hostname)) {}
 void send(string soapMsg) {sock_ << soapMsg;}
 ~HttpRequest ( ) {delete sock_;}
private:
 Socket* sock_;
};

void sendMyData(string soapMsg, string host) {
 HttpRequest req(host);
 req.send(soapMsg);
 // Nothing to do here, because when req goes out of scope
 // everything is cleaned up.
}

int main( ) {
 string s = "xml";
 sendMyData(s, "www.oreilly.com");
}

 

Discussion

The guarantees made by constructors and destructors offer a nice way to let the compiler clean up after you. Typically, you initialize an object and allocate any resources it uses in the constructor, and clean them up in the destructor. This is normal. But programmers have a tendency to use the create-open-use-close sequence of events, where the user of the class is required to do explicit "opening" and "closing" of resources. A file class is a good example.

The usual argument for RAII goes something like this. I could easily have designed my HttpRequest class in Example 8-3 to make the user do a little more work. For example:

class HttpRequest {
public:
 HttpRequest ( );
 void open(const std::string& hostname);
 void send(std::string soapMsg);
 void close( );
 ~HttpRequest ( );
private:
 Socket* sock_;
};

With this approach, a responsible version of sendMyData might look like this:

void sendMyData(std::string soapMsg, std::string host) {
 HttpRequest req;

 try {
 req.open( );
 req.send(soapMsg);
 req.close( );
 }
 catch (std::exception& e) {
 req.close( );
 // Do something useful...
 }
}

This is more work without any benefit. This sort of design forces the user to write more code and to deal with exceptions by cleaning up your class (assuming you don't call close in your destructor).

The RAII approach has wide applicability, especially when you want a guarantee that something will be undone if an exception is thrown without having to put TRy/catch code all over the place. Consider a desktop application that wants to display a message on the status bar or title bar while some work is being done:

void MyWindow::thisTakesALongTime( ) {
 StatusBarMessage("Copying files...");
 // ...
}

All the StatusBarMessage class has to do is update the appropriate window with status information when it is constructed, and reset it back to the empty string (or whatever message was there previously) when it is destroyed. Here's the key point: if the function returns or an exception is thrown StatusBarMessage still gets its work done. The compiler guarantees that the destructor will be called for a stack variable whose scope has exited. Without this approach, the author of thisTakesALongTime needs to carefully account for every control path so the wrong message doesn't remain on the window if the operation fails, the user cancels it, etc. Once again, this results in less code and fewer errors for the author of the calling function.

RAII is no panacea, but if you have not used it before, chances are you can find a number of places where it is useful. Another good example is locking. If you are using RAII to manage locks on resources such as threads, pooled objects, network connections, etc., you will find that this approach allows for stronger exception-safety and less code. In fact, this is how the Boost multithreading library implements locks to make for clean programming on the part of the user. See Chapter 12 for a discussion of the Boost Threads library.

Building C++ Applications

Code Organization

Numbers

Strings and Text

Dates and Times

Managing Data with Containers

Algorithms

Classes

Exceptions and Safety

Streams and Files

Science and Mathematics

Multithreading

Internationalization

XML

Miscellaneous

Index



C++ Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2006
Pages: 241

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net