C++ – Conceptual Difference Between Finally and Destructor

cexceptionsfinally

First, I am well aware of Why is there no 'finally' construct in C++? but a lengthy-growing comment discussion on another question seems to warrant a separate question.

Apart from the issue that finally in C# and Java can basically exist only once (== 1) per scope and a single scope can have multiple (== n) C++ destructors, I think that they are essentially the same thing. (With some technical differences.)

However, another user argued:

… I was trying to say that a dtor is inherently a tool for (Release sematics) and
finally is inherently a tool for (Commit semantics). If you don't see why: consider
why it's legitimate to throw exceptions on top of each other in
finally blocks, and why the same is not for destructors. (In some
sense, it's a data vs. control thing. Destructors are for releasing
data, finally is for releasing control. They are different; it's
unfortunate that C++ ties them together.)

Can someone clear this up?

Best Answer

  • Transaction (try)
  • Error Output/Response (catch)
  • External Error (throw)
  • Programmer Error (assert)
  • Rollback (closest thing might be scope guards in languages that support them natively)
  • Releasing Resources (destructors)
  • Misc Transaction-Independent Control Flow (finally)

Can't come up with a better description for finally than misc transaction-independent control flow. It doesn't necessarily map so directly to any high-level concept in the context of a transaction and error recovery mindset, especially in a theoretical language that has both destructors and finally.

What's most inherently lacking to me is a language feature that directly represents the concept of rolling back external side effects. Scope guards in languages like D are the closest thing I can think of that comes close to representing that concept. From a control flow standpoint, a rollback in a particular function's scope would need to distinguish an exceptional path from a regular one, while simultaneously automating the rollback implicitly of any side effects caused by the function should the transaction fail, but not when the transaction succeeds. That's easy enough to do with destructors if we, say, set a boolean to value like succeeded to true at the end of our try block to prevent the rollback logic in a destructor. But it's a rather roundabout way to do this.

While that might seem like it wouldn't save so much, side effect reversal is one of the hardest things to get right (ex: what makes it so difficult to write an exception-safe generic container).