Example
This section provides a series of examples to illustrate how the use of a scope guard can aid in error handling.
Let’s consider this simple example that opens a file and inserts it into a cache.
void f (const char* file_name)
{
FILE* f = fopen(file_name, "rb+");
mutex.acquire();
file_cache.append(f);
mutex.release();
}
The above code has no error handling and is not safe. It does not consider control flow or what happens after the file is opened. If an exception is thrown or the function exits early, nothing ensures that the file is closed or that the mutex is released. We can fix this by reorganizing the code or adding a few try/catch blocks to handle exceptions.
void f (const char* file_name)
{
FILE* f = fopen(file_name, "rb+");
// this try/catch ensures that 'f' is closed
try {
mutex.acquire();
// this try/catch ensures that 'mutex' is released
try {
file_cache.append(f);
}
catch(...) {
mutex.release();
throw;
}
mutex.release();
} catch(...) {
fclose (f);
throw;
}
}
That’s an improvement -- except the code is now less efficient and more complicated. And this is a simple application; the more complex the flow, or special cases we have to handle, the more complicated the code will become.
To make this code safe is trivial, and the additional overhead is small.
void f (const char* file_name)
{
FILE* f = fopen(file_name, "rb+");
// this guard ensures 'f' is closed on exit from scope.
RWScopeGuard file_guard = rwtMakeScopeGuard(fclose, f); //1
mutex.acquire();
// this guard ensures 'mutex' is released on exit from scope.
RWScopeGuard lock_guard = rwtMakeScopeGuard(mutex, &Mutex::release); //2
file_cache.append(f); //3
file_guard.dismiss(); //4
}
Now our code safely, cleanly and efficiently ensures that all resources are cleaned up.