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?

Martin Ba
  • 7,578
  • 7
  • 34
  • 56

5 Answers5

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

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.

user541686
  • 8,074
  • 8
  • 38
  • 49
  • 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 02 '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 02 '15 at 11:00
  • @BartvanIngenSchenau: Is that even a fact? In which language? And why should it factor into this? – user541686 Dec 02 '15 at 11:05
  • @MartinBa: Okay =P let me guess, you're going to argue the first case is too artificial, and that e.g. there must be some kind of "acquire" operation paired with the finally block? – user541686 Dec 02 '15 at 11:05
  • @Mehrdad: To my knowledge, `finally` was introduced into Java to make it possible to release resources that wouldn't be reliably reclaimed by the GC. Also, I am not aware of any languages that have both `finally` and destructors. Is there any language where your separation can be reliably used? – Bart van Ingen Schenau Dec 02 '15 at 11:37
  • 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](http://dlang.org/spec/grammar.html) 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`. – user541686 Dec 02 '15 at 11:46
  • @Mehrdad: I think the distinction you are trying to make is too subtle for most people, especially if they have only been exposed to languages where you can't make that distinction in practice. – Bart van Ingen Schenau Dec 02 '15 at 12:05
  • @BartvanIngenSchenau: Is it that subtle? To me it seems like there's a pretty obvious rule of thumb: if you're releasing a resource, use destructors; otherwise use `finally`. I feel like that's clear in most if not all cases? – user541686 Dec 02 '15 at 12:19
  • I agreed before I thought your argument through, but now I am not so sure. In the example (which I have no doubt is not intended as a real use case) I would argue that `finally` is just as wrong as a destructor. When I tried to mentally fill in scenarios where it wouldn't be enough to just return from the function without `finally`, I couldn't do it without introducing a resource of some sort to dispose of. And it's always something I can't expect a destructor or gc to work on. So now I am thinking that `finally` is The Hack Destructor for resources beyond GC. – sqykly Dec 02 '15 at 13:53
  • @sqykly: I don't understand, why do you say finally is just as wrong as a destructor? What do you think is the right way to achieve that kind of goal? Also, see my second example above. As it shows again, there *really* doesn't need to be a resource that needs to be released in the finally block. – user541686 Dec 02 '15 at 17:23
  • Mehrdad - actually I think your distinction doesn't make sense. I don't think finally and d'tors solve much different problems at all. (Well I think d'tors are much more powerful.) But I've added some thoughts and links to my own answer. Feel free to comment. :-) – Martin Ba Dec 02 '15 at 19:39
  • In the first example, I was thinking the block was superfluous. – sqykly Dec 03 '15 at 07:30
  • 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. – user541686 Dec 18 '15 at 22:09
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.

gbjbaanb
  • 48,354
  • 6
  • 102
  • 172
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
}
k3b
  • 7,488
  • 1
  • 18
  • 31
  • 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 03 '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. –  Dec 18 '15 at 08: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. –  Dec 18 '15 at 08:29
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.)

Martin Ba
  • 7,578
  • 7
  • 34
  • 56
  • What are your thoughts on my Javascript example? – user541686 Dec 02 '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. – user541686 Dec 02 '15 at 20:22
  • You never addressed these... – user541686 Dec 08 '15 at 04: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 08 '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 08 '15 at 20:52
  • I don't understand, my second example had nothing to do with throwing from a finally, so the fact that you think that's a bad idea should be irrelevant. (By which I mean, even if it never threw, the same argument would apply.) – user541686 Dec 08 '15 at 20:57
  • @Mehrdad - let's continue at http://chat.stackexchange.com/rooms/32696/finally-vs-dtor (note: will be offline soon today, but will reply during next days) – Martin Ba Dec 08 '15 at 21:01