12

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?

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

4

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.

4

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
}
  • In C++, why is the destructor called automatically only with a stack object and not with a heap object in case of an exception? – Giorgio Dec 3 '15 at 19:02
  • @Giorgio Because heap resources live in a memory space that isn't directly tied to the call stack. For example, imagine a multithreaded application with 2 threads, A and B. If one thread throws, rolling back A's transaction should not destroy resources allocated in B, e.g. -- the thread states are independent of each other, and persistent memory living on the heap is independent of both. However, typically in C++, heap memory is still tied to objects on the stack. – user204677 Dec 18 '15 at 8:27
  • @Giorgio For example, an std::vector object might live on the stack but point to memory on the heap -- both the vector object (on the stack) and its contents (on the heap) would be deallocated during a stack unwind in that case, since destroying the vector on the stack would invoke a destructor which frees the associated memory on the heap (and likewise destroy those heap elements). Typically for exception-safety, most C++ objects live on the stack, even if they're only handles pointing to memory on the heap, automating the process of freeing both heap and stack memory on stack unwind. – user204677 Dec 18 '15 at 8:29
3

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.

  • Thanks. I disagree, but hey :-) I think I'll be able to add a thorough opposing view answer in the next days ... – Martin Ba Dec 2 '15 at 10:47
  • 2
    How does the fact that finally is mostly used to release (non-memory) resources factor into this? – Bart van Ingen Schenau Dec 2 '15 at 11:00
  • 1
    @BartvanIngenSchenau: I never argued that any language currently in existence has a philosophy or implementation that matches what I described. People haven't finished inventing everything that could possibly exist yet. I only argued that there would be value in separating the two notions as they are different ideas and have different use cases. To satisfy your curiosity, I do believe D has both. There are probably other languages too. I don't consider it relevant though, and I couldn't care less why e.g. Java was in favor of finally. – Mehrdad Dec 2 '15 at 11:46
  • 1
    A practical example I've encountered in JavaScript are functions that temporarily change the mouse pointer to an hourglass during some lengthy operation (which might throw an exception), and then change it back to normal in the finally clause. The C++ worldview would introduce a class that manages this “resource” of an assignment to a pseudo-global variable. What conceptual sense does that make? But destructors are C++'s hammer for required end-of-block code execution. – dan04 Dec 18 '15 at 21:44
  • 1
    @dan04: Thanks so much, that's the perfect example for this. I could swear I'd come across so many situations where RAII didn't make sense but I had such a hard time thinking of them. – Mehrdad Dec 18 '15 at 22:09
1

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

  • What are your thoughts on my Javascript example? – Mehrdad Dec 2 '15 at 19:56
  • Regarding your other arguments: "Would we really want to log the same thing irregardless?" Yes, it's just an example and you're kind of missing the point, and yes, no one ever prohibited from logging more specific details for each case. The point here is that you certainly can't claim there is never a situation in which you would want to log something that is common to both. Some log entries are generic, some are specific; you want both. And again, you're kind of completely missing the point by focusing on the logging. Motivating 10-line examples is hard; please try to not miss the point. – Mehrdad Dec 2 '15 at 20:22
  • You never addressed these... – Mehrdad Dec 8 '15 at 4:27
  • @Mehrdad - I didn't address your javascript example because it would take me another page to discuss what I think of it. (I tried, but it took me so long, to phrase something coherent that I skipped it :-) – Martin Ba Dec 8 '15 at 20:48
  • @Mehrdad - as for your other points - it seems we have to agree to disagree. I see where you're aiming at with the difference, but I'm just not convinced that they are something conceptually different: Mainly because I'm mostly in the camp that thinks throwing from finally is a really bad idea (note: I also think in your observer example that throwing there would be a really bad idea.) Feel free to open a chat, if you want to discuss this further. It was certainly fun thinking about your arguments. Cheers. – Martin Ba Dec 8 '15 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.