I was reading up about exception safe code in C++ and the difference between the strong and basic exception guarantees. The article here (http://www.gotw.ca/gotw/082.htm) outlines what it means for code to satisfy the basic exception safety guarantee, however it doesn’t give an example. So I came up with something that is exception unsafe. The code below doesn’t satisfy the basic guarantee because it fails to delete the memory allocated to the pointer.
Exception Unsafe
#include <iostream>
#include <stdexcept>
using namespace std;
void h()
{
throw logic_error((string("Failure in ") + string(__func__)));
}
void g()
{
int* pi = new int;
h();
delete pi;
}
void f()
{
try
{
g();
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
int main(int argc, char** argv)
{
f();
}
To ensure that it meets the basic guarantee, the exception needs to be caught in g()
and the memory deallocated. The code below does this. Unfortunately, there is a bit of awkwardness here: note the double delete. One is in the bad path where the exception is caught and the other is in the good code path. It would be nice if the double delete wasn’t necessary. Other languages have a finally
statement that is executed in both the good and bad case, however C\++ doesn’t have this but the problem is still solvable as shown below.
Basic Exception Safety Met
#include <iostream>
#include <stdexcept>
using namespace std;
void h()
{
throw logic_error((string("Bad logic in ") + string(__func__)));
}
void g()
{
int* pi = new int;
try
{
h();
}
catch (const exception& e)
{
cout << "Caught exception: \"" << e.what() << "\" in " << __func__ << endl;
delete pi;
throw;
}
delete pi;
}
void f()
{
try
{
g();
}
catch (const exception& e)
{
cout << "Caught exception: \"" << e.what() << "\" in " << __func__ << endl;
}
}
int main(int argc, char** argv)
{
f();
}
The lack of a finally
statement in C++ isn’t a problem as long as the principle Resource Acquisition is Initialization is applied. It’s fairly straightforward and with a little bit of thought, I think that most good programmers would realize what to do here in order to avoid having a double delete. The trick is to write a wrapper class to manage the resource and in the destructor, clean up any allocated memory. If you follow this guideline, then there is no need for C++ to have a finally
statement. This is mentioned in the C++ FAQ in section 17.6. The following code applies RAII. I’ve found myself doing this in code I’ve written without explicitly realizing the technique I was applying had a name associated with it.
Basic Exception Safety with RAII
#include <iostream>
#include <stdexcept>
using namespace std;
class Resource
{
private:
int* pi;
public:
Resource()
{
cout << __func__ << endl;
pi = new int;
}
~Resource()
{
cout << __func__ << endl;
delete pi;
}
};
void h()
{
throw logic_error((string("Bad logic in ") + string(__func__)));
}
void g()
{
Resource r;
h();
}
void f()
{
try
{
g();
}
catch (const exception& e)
{
cout << "Caught exception: \"" << e.what() << "\" in " << __func__ << endl;
}
}
int main(int argc, char** argv)
{
f();
}