Exceptions are an exciting addition to C++ because they allow you to write clearer and safer code. Many C++ experts will also tell you that by using exceptions, you’ll end up with code that’s both easier to maintain and that contains fewer bugs. I firmly agree, although with a few caveats.
Improperly used, exceptions can lead to resource leaks that are very hard to find. In this article, we’ll present a technique called resource allocation is initialization. This technique encapsulates the allocation and eventual deallocation of resources, freeing you from doing it yourself.
bool process_file(const char *fname)
{
FILE *f;
if ((f = fopen(fname, “wb”)) == 0)
return false;
read_file(f);
fclose(f);
return true;
}
At first glance, this function doesn’t appear to have a problem. However, there’s a potential for leaking a file handle. What if the read_file function throws an exception? Since there’s no try/catch block protecting the call to fclose, any exception thrown by read_file won’t close the file handle.
You might try to remedy this dilemma as follows:bool process_file(const char *fname)
{
FILE *f;
if ((f = fopen(fname, “wb”)) == 0)
return false;
try
{
read_file(f);
}
catch (…)
{
fclose(f);
throw; /* rethrow exception */
}
fclose(f);
return true;
}
While this approach solves the resource leak, it’s tiresome, prone to error, and not very elegant. Thankfully, there’s an easier way to resolve the problem.
Remember the property that automatic variables have? Their destructors are called whenever they fall out of scope, including each time an exception is thrown. With this in mind, I’ll introduce the following class:class File
{
FILE *p;
public:
File(const char *fname, char *mode)
{
if ((p = fopen(fname, mode)) == 0)
throw “Error opening file”;
}
~File() { fclose(p); }
operator FILE*() { return p; }
};
With this class, the function becomes trivial (and safer):
void process_file(const char *fname)
{
File f(fname, “wb”);
read_file(f);
}
By encapsulating the allocation and deallocation of the file handle into a class and taking advantage of the inherent properties of destructors, we’ve removed from you the responsibility of freeing the file handle. Now, regardless of whether an exception is thrown, the file handle will always be freed.
Note that the File class contains a casting operator that allows the class to function exactly like a FILE pointer. Therefore, the following code is still perfectly legal:void f(void)
{
File f(“test.tmp”,”rb”);
char buffer[80];
fread(buffer, sizeof(buffer), 1, f);
…
}
templateThis template allows you to encapsulate all your memory allocations in such a way as to guarantee that they’re freed when they fall out of scope, as follows:class Memory { T *p; public: Memory(int size) { p = new T[size]; } ~Memory() { delete [] p; } operator T*() { return p; } };
void f(void)
{
Memory buffer(512);
//…use buffer
}
As you can see, you can easily extend this solution to any type of resource: file handles, memory, locks, semaphores, and database cursors, just to name a few.
void __fastcall TForm::ButtonClick(
TObject *Sender)
{
auto_ptr Query(new TQuery(this));
//…use Query
}