In this post I want to describe an interesting observation: programmers generally use destructors for two purposes. One is fairly obvious: releasing resources; the other — not necessarily so.

One thing that can be objectively said about destructors (i.e., avoiding giving any advice, or judging what is a good/bad practice) is that they are a function that is executed when the object’s life-time ends, and that the call to this function is not visible in the source code.

But it would be unfair to say that a destructor is just an ordinary function. It has one essential special property: throwing exceptions from it is, well… tricky. And it has become even trickier since 2011: unless you explicitly override the default behaviour, an attempt to throw from a destructor results in a call to std::terminate . See this post for details.

This restriction on exceptions does not impede the primary use case for destructors: releasing resources. At this point some readers may object: but releasing a resource may fail too, can’t it? And don’t we signal such failures with exceptions? Let me try to address this concern.

The C++ Standard only defines how exceptions work and interact with other features. The questions like what they are for, where should we use them an where not, are only a matter of opinion or a personal experience. So, I can only offer my personal point of view. Here it is. Exceptions are not meant to simply signal a run-time failure. You use exceptions in the situation where you observe that due to the unavailability of some resources (or for some other, less frequent reasons) performing some actions/computations is impossible or makes no sense, and has to be skipped. Consider:

int fun() { ResourceUser r{params}; return operate(r); }

When the acquisition of the resources required by r is impossible you throw an exception. But this is not because you want to broadcast the information that your attempt to acquire the resource failed. You do it because you want to signal that function operate cannot and will not be called; next, to indicate that function fun cannot and will not do what it advertises. Next, consider the outer scope:

int outer_fun() { int i = fun(); int j = gun(i); return hun(j); }

You throw because in the event of fun not calling operate , you do not want to call gun or hun because they no longer make sense. And consequently, the execution of outer_fun is also ‘cancelled’, as well as the execution of anyone who relies on its results. This avalanche is stopped at some point by an exception handler (a catch -statement): it separates the ‘cancelled’ operations from these that can still be performed.

The point is: you throw not because you want to signal the status of the resources, but because you want to trigger the cancellation of operations. You want some portion of the program to be skipped. The exceptions mechanism gives us a nice tool for controlling how the cancellation avalanche proceeds. Now, let’s go back to our example:

int fun() { ResourceUser r{params}; return operate(r); }

Suppose the resource acquisition succeeded and you have successfully evaluated function operate , you have the return value. Before you release the control to the caller, you try to release the resource and you get the feedback that the system didn’t confirm that the resource is released. Sure, it is not nice, but there is no need to cancel anything. Function fun is capable of doing what it advertises: it will return the correct result. Functions gun and hun can be correctly evaluated and in turn, outer_fun can deliver the correct result. You do not need to cancel any operation. True, some other function in the chain (call it foo ) can later also attempt to acquire the resource you failed to release, but then it will be reported as foo ’s failure to do what it advertises.

For diagnostic purposes, it may be useful to report (somehow) the failure to release a resource, but you need not involve the exception mechanism for this: you do not need to disturb the program logic flow.

You may be convinced or not by the above reasoning, but I think it illustrates well why the restriction on throwing from destructors is not severe, provided you are using destructors for releasing resources.

Destructors that do not release

Another usage of destructors, not uncommon, is to perform a normal action connected to our class’s logic which is naturally the last task to be performed and we do not want to bother users with typing (and possibly forgetting) the obvious. For one example, consider this implementation of DB transaction:

int fun() { DBTransaction trx{parameters}; trx.execute(sql1); trx.execute(sql2); } // commit or cancel in destructor

For another example, consider a utility class for temporarily storing the value of a given object and later restoring it in the object at the end of the scope:

int doWhatIfAnalysis(Model& m) { StateSaver _{m}; // stores the copy of m's state change(m); return analyse(m); } // restore the saved state of m in destructor

In each of these cases, in destructor, we perform some program logic advertised by the class, which can fail, and in such cases we want to launch the ‘cancellation cascade’: we need and want to throw exceptions. And here is where the programmer is really hit by the exception mechanism rules. Counter to what some people think, throwing from destructors is allowed and well defined (see this post). However, there is still one issue that remains: the potential double exception issue. I do not want to dive into how the programmers address it. I do not want to advertise this technique too much. I just note that some programmers use destructors this way to the extent that changes to the C++ language are proposed that would allow to check if a destructor is called due to the stack unwinding or not. One of the readers called this technique “logic completion”.

The first use case (resource release) can be safely recommended — it is well thought over in every detail. The second use case is at least controversial — it has some unresolved issues with how to handle destructor failures.