Question

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?

Was it helpful?

Solution

  • 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).

OTHER TIPS

In a way they are - in the same way that a Ferrari and a transit can both be used to nip down the shops for a pint of milk even though they're designed for different uses.

You could place a try/finally construct in every scope and clean up all scope defined variables in the finally block to emulate a C++ destructor. This is, conceptually, what C++ does - the compiler automatically calls the destructor when a variable goes out of scope (ie at the end of the scope block). You'd have to arrange your try/finally so the try is the very first thing and the finally the very last thing in each scope however. You'd also have to define a standard for each object to have a specifically-named method that it uses to clean up its state that you would call in the finally block, though I guess you could leave the normal memory management your language provides to clean up the now-emptied object when it likes.

It wouldn't be pretty to do this though, and even though .NET introduced IDispose as a manually managed destructor, and using blocks as an attempt to make that manual management slightly easier, its still not something you'd want to do in practice.

From my point of view the main difference is that a destructor in c++ is an implicit mechanism (automatically invoked) for releasing allocated resources while the try ... finally can be used as an explicit mechanism to do that.

In c++ programmes the programmer is responsible for releasing allocated resources. This is usually implemented in the destructor of a class and done immediately when a variable goes out of scope or or when delete is called.

When in c++ a local variable of a class is created without using new the resources of that instances are freed implicit by the destructor when there is an exception.

// c++
void test() {
    MyClass myClass(someParameter);
    // if there is an exception the destructor of MyClass is called automatically
    // this does not work with
    // MyClass* pMyClass = new MyClass(someParameter);

} // on test() exit the destructor of myClass is implicitly called

In java, c# and other systems with an automatic memory management the systems garbage collector decides when a class instance is destroyed.

// c#
void test() {
    MyClass myClass = new MyClass(someParameter);
    // if there is an exception myClass is NOT destroyed so there may be memory/resource leakes

    myClass.destroy(); // this is never called
}

There is no implicit mechanism to that so you have to program this explicitly using try finally

// c#
void test() {
    MyClass myClass = null;

    try {
        myClass = new MyClass(someParameter);
        ...
    } finally {
        // explicit memory management
        // even if there is an exception myClass resources are freed
        myClass.destroy();
    }

    myClass.destroy(); // this is never called
}

Glad you posted this as a question. :)

I was trying to say that destructors and finally are conceptually different:

  • Destructors are for releasing resources (data)
  • finally is for returning to the caller (control)

Consider, say, this hypothetical pseudo-code:

try {
    bar();
} finally {
    logfile.print("bar has exited...");
}

finally here is solving entirely a control problem and not a resource management problem.
It wouldn't make sense to do that in a destructor for a variety of reasons:

  • No thing is being "acquired" or "created"
  • Failure to print to the log file will not result in resource leaks, data corruption, etc. (assuming that the logfile here is not fed back into the program elsewhere)
  • It is legitimate for logfile.print to fail, whereas destruction (conceptually) cannot fail

Here's another example, this time like in Javascript:

var mo_document = document, mo;
function observe(mutations) {
    mo.disconnect();  // stop observing changes to prevent re-entrance
    try {
        /* modify stuff */
    } finally {
        mo.observe(mo_document);  // continue observing (conceptually, this can fail)
    }
}
mo = new MutationObserver(observe);
return observe();

In the above example, again, there are no resources to be released.
In fact, the finally block is acquiring resources internally to achieve its goal, which could potentially fail. Hence, it doesn't make sense to use a destructor (if Javascript had one).

On the other hand, in this example:

b = get_data();
try {
    a.write(b);
} finally {
    free(b);
}

finally is destroying a resource, b. It's a data problem. The problem is not about cleanly returning control to the caller, but rather about avoiding resource leaks.
Failure is not an option, and should (conceptually) never occur.
Every release of b is necessarily paired with an acquisition, and it makes sense to use RAII.

In other words, just because you can use either to simulate either that doesn't mean both are one and the same problem or that both are appropriate solutions for both problems.

k3b's answer really phrases it nicely:

a destructor in c++ is an implicit mechanism (automatically invoked) for releasing allocated resources while the try ... finally can be used as an explicit mechanism to do that.

As for "resources", I like to refer to Jon Kalb: RAII should mean Responsibility Acquisition Is Initialization.

Anyways, as for implicit vs. explicit this seems really to be it:

  • A d'tor is a tool to define what operations shall happen - implicitly - when the lifetime of an object ends (which often coincides with end-of-scope)
  • A finally-block is a tool to define - explicitly - what operations shall happen at end-of-scope.
  • Plus, technically, you are always allowed to throw from finally, but see below.

I think that's it for the conceptual part, ...


... now there are IMHO some interesting details:

I do also not think that c'tor/d'tor need to conceptually "acquire" or "create" anything, apart from the responsibility to run some code in the destructor. Which is what finally does also: run some code.

And while code in a finally block certainly can throw an exception, that's not enough distinction to me to say that they are conceptually different above explicit vs. implicit.

(Plus, I'm not convinced at all that "good" code should throw from finally -- maybe that's another whole question to itself.)

Licensed under: CC-BY-SA with attribution
scroll top