TITLE: more musings on exception safety PROBLEM: Stan Sulsky I wonder if anyone has a good working definition of "execption safe?" RESPONSE: rmartin@oma.com (Robert C. Martin), 11 Nov 95 I have one: "Exception safety is very hard." The main problem is that C++, while it automatically provides for the propogation of exceptions, and the automatic destruction of stack variables, does not automatically provide exception safety. Exception safety must be maintained by convention; and convention always eventually fails. The primary convention in exception safe software is: "Resource acquistion is initialization." You can read about this in Stroustrup's "Design and Evolution of C++", and many other latter books on C++. The main idea is that in any block of code through which an exception may pass, any resource should be allocated in the constructor of a stack variable, and deallocated in the destructor of that same variable. Thus, when an exception passes through the block, all resources will be deallocated because all the destructors of all the stack variables will be called. i.e. class Deallocator { public: Deallocator(char* p) : itsP(p) {} virtual ~Deallocator() {delete [] itsP;} operator char*() {return itsP;} private: char* itsP; }; void f() throw X { Deallocator p(new char[20]); ... throw X; } It should be clear that the variable p can be used as a surrogate pointer to the allocated buffer of 20 bytes, without any implementation overhead. Yet when the throw of X takes place, p is destructed, and the buffer is deleted by p's destructor. I have to re-stress that every -- every function through which an exception may pass must allocate all its resources in this manner. This includes constructors which allocate resources for member variables. Constructors can throw exceptions. When they do, the corresponding destructor is not called. The compiler makes the following guarantees. All member variables and all base classes will be destructed. Therefore, those member variables which represent resources that are acquired during the execution of a constructor must be allocated in such a way that when the member variable is destructed, the resource is deallocated. For example: class C { public: C() throw X; private: Deallocator itsBuffer; } C::C() throw X : itsBuffer(new char[20]) { } The compiler guarantees that, if an exception is thrown from a constructor that was called as a result of the use of the 'new' keyword. (i.e. the object is being allocated using 'new') then the corresponding 'operator delete' will be called. Thus: try { C* cp = new C; } catch ... { // don't worry, the storage allocated for cp was already deleted. } However, what if "placement operator new" was used? In that case, and only in that case, the compiler will call the corresponding placement operator delete. void* operator new(size_t, void* p) {return p;} // std placement new. void operator delete(void*, void*) {} // placement delete try { char cArea[sizeof C]; new(cArea) C; // places the C in cArea. } catch ... { // don't worry, placement operator delete was called, so the // compiler did not attempt to return cArea to the heap! } Now. If you forget to use the 'Deallocator' mechanism for things like memory allocation/deallocation, file open/close, mutex sieze/release, etc, etc, then you will have problems when exceptions are thrown. If you forget to create appropriate placement delete operators for every placement new through which an exception can pass, then you will have problems. This is a burden of convention that is very severe. IMHO, it makes the use of exceptions in C++ nearly untennable. I will never be able to trust that all the engineers will remember to use the conventions in every single case. Which means that in one of a hundred, or one of a thousand instances, it will be done wrong. One day an exception will propogate through a bad stretch of code, and the system will lock up or crash. This is the worst possible kind of bug. I fear that supposedly exception safe code will be littered with these kinds of bugs. They will be extremely difficult to find, since exceptions can propogate anywhere. And they can have catastrophic consequences. So, I am dubious that exceptions can really be used effectively in C++. There is another problem as well. Many systems are hybrid, based upon several different software packages. For example, Windows programs use the windows API. Exceptions will not properly propogate through stack frames built by software packages that don't know anything about exceptions. Thus, you cannot throw an exception down in the guts of a Windows event handler, and expect it to be caught in the main idle loop.