Live data from Hacker News

C++ says “We have try... finally at home”

devblogs.microsoft.com

141–150 of 152 posts

Re: C++ says “We have try... finally at home”

#141
post #94

Earlier quoted context omitted.

The entire point of the article is that you cannot throw from a destructor. Now how do you signal that closing/writing the file in the destructor failed?

Just panic. What's the caller realistically going to do with that information?

[deleted]

Re: C++ says “We have try... finally at home”

#143
post #139

Earlier quoted context omitted.

They are related but fundamentally different. It is a vital semantic difference (influencing the programming model itself) since destructors (C++ style) are synchronous and deterministic while finalizers (Java style) are asynchronous and non-deterministic. It is because of all the problems that the finalize method was deprecated in Java 9 and marked "deprecated for removal"(JEP 421) in Java 18. More details at https:…

I grasp the entirety of why people differentiate "finalizers" from "destructors", but in my opinion, the practical differences in their application are not the result of the concept itself being fundamentally different, it's a result of object lifetimes being different between GC'd and non-GC'd languages. In my opinion, the concept itself is pretty close to identical. You want to clean up resources at the end of the…

> not the result of the concept itself being fundamentally different, it's a result of object lifetimes being different between GC'd and non-GC'd languages.

This is the fundamental misunderstanding. The RAII ctor/dtor pattern is a very general mechanism not limited to just managing object (in the OO sense) lifetimes. That is why you don't need finally/defer etc. in C++. You can get all of these policies using just this one mechanism.

The correct way to think about it is as scoped entry and exit function calls i.e. a scoped guard. For example, every C++ programmer writes a LogTrace class to log function (or other scope) entry and exit messages. This is purely exploiting the feature to make function calls with nothing whatever to do with objects (in the sense of managing state) at all. Raymond gives a good example when he points to how wil::scope_exit takes a user-defined lambda function to be run by a dummy object's dtor when it goes out of scope.

> I don't view RAII/scoped destructors as being equivalent or alternatives to things like `finally` blocks or `defer` statements. In C++ you basically use scope guards for everything because they are the only option, but I think C++ would still ultimately benefit from at least having `finally`.

Scope guards using ctor/dtor mechanism is enough to implement all the policies like finally/defer etc. That was the point of the article.

> You can kind of emulate it, but not 100%: `finally` blocks are outside of the scope of the exception and can throw a new exception, unlike a destructor in an exception frame. Having more options in structured control flow can sometimes add complexity for little gain, but `finally` can genuinely be useful sometimes.

The article already points out the main issues (in both non-GC/GC languages) here but it is actually much more nuanced. While it is advised not to throw exceptions from a dtor C++ does give you std::uncaught_exceptions() which one can use for those special times when you must handle/throw exceptions in a dtor. More details at - https://stackoverflow.com/questions/74607300/should-i-use-st... and https://en.cppreference.com/w/cpp/error/uncaught_exception.h...

Exception handling is always tricky to implement/use in any language since there are multiple models (i.e. Termination vs. Resumption) and a language designer is often constrained in his choice. Wikipedia has a very nice explanation - https://en.wikipedia.org/wiki/Exception_handling_(programmin... In particular, see the Eiffel contract approach mentioned in it and then the detailed rationale in Bertrand Meyer's OOSC2 book - https://bertrandmeyer.com/OOSC2/

Re: C++ says “We have try... finally at home”

#144

Earlier quoted context omitted.

The entire point of the article is that you cannot throw from a destructor. Now how do you signal that closing/writing the file in the destructor failed?

> The entire point of the article is that you cannot throw from a destructor. You need to read the article again because your assertion is patently false. You can throw and handle exceptions in destructors. What you cannot do is not catch those exceptions, because as per the standard uncaught exceptions will lead the application to be immediately terminated.

You can throw in a destructor but not from one, as the quoted text rightly notes.

Re: C++ says “We have try... finally at home”

#145

Destructors are vastly superior to the finally keyword because they only require us to remember a single time to release resources (in the destructor) as opposed to every finally clause. For example, a file always closes itself when it goes out of scope instead of having to be explicitly closed by the person who opened the file. Syntax is also less cluttered with less indentation, especially when multiple objects are…

The scope guard statement is even better! https://dlang.org/articles/exception-safe.html https://dlang.org/spec/statement.html#ScopeGuardStatement Yes, D also has destructors.

I use this library a lot for scope guards in C++ https://github.com/Neargye/scope_guard, especially for rolling back state on errors, e.g.

In a function that inserts into 4 separate maps, and might fail between each insert, I'll add a scope exit after each insert with the corresponding erase.

Before returning on success, I'll dismiss all the scopes.

I suppose the tradeoff vs RAII in the mutex example is that with the guard you still need to actually call it every time you lock a mutex, so you can still forget it and end up with the unreleased mutex, whereas with RAII that is not possible.

Re: C++ says “We have try... finally at home”

#147
post #88
post #59

Earlier quoted context omitted.

Any fallible cleanup function is awkward, regardless of error handling mechanism.

Java solved it by having exceptions be able to attach secondary exceptions, in particular those occurring during stack unwinding (via try-with-resources). The result is an exception tree that reflects the failures that occurred in the call tree following the first exception.

I often miss this feature in other languages. It has saved me more times than I can count.

Re: C++ says “We have try... finally at home”

#148

Earlier quoted context omitted.

The scope guard statement is even better! https://dlang.org/articles/exception-safe.html https://dlang.org/spec/statement.html#ScopeGuardStatement Yes, D also has destructors.

I use this library a lot for scope guards in C++ https://github.com/Neargye/scope_guard , especially for rolling back state on errors, e.g. In a function that inserts into 4 separate maps, and might fail between each insert, I'll add a scope exit after each insert with the corresponding erase. Before returning on success, I'll dismiss all the scopes. I suppose the tradeoff vs RAII in the mutex example is that with th…

The tradeoff with RAII is once you have 3 things that must all succeed or all fail, that is very clumsy to do with RAII, but easy with scope guard.

Re: C++ says “We have try... finally at home”

#149
post #139

Earlier quoted context omitted.

I grasp the entirety of why people differentiate "finalizers" from "destructors", but in my opinion, the practical differences in their application are not the result of the concept itself being fundamentally different, it's a result of object lifetimes being different between GC'd and non-GC'd languages. In my opinion, the concept itself is pretty close to identical. You want to clean up resources at the end of the…

> not the result of the concept itself being fundamentally different, it's a result of object lifetimes being different between GC'd and non-GC'd languages. This is the fundamental misunderstanding. The RAII ctor/dtor pattern is a very general mechanism not limited to just managing object (in the OO sense) lifetimes. That is why you don't need finally/defer etc. in C++. You can get all of these policies using just th…

> This is the fundamental misunderstanding. The RAII ctor/dtor pattern is a very general mechanism not limited to just managing object (in the OO sense) lifetimes. That is why you don't need finally/defer etc. in C++. You can get all of these policies using just this one mechanism.

> The correct way to think about it is as scoped entry and exit function calls i.e. a scoped guard. For example, every C++ programmer writes a LogTrace class to log function (or other scope) entry and exit messages. This is purely exploiting the feature to make function calls with nothing whatever to do with objects (in the sense of managing state) at all. Raymond gives a good example when he points to how wil::scope_exit takes a user-defined lambda function to be run by a dummy object's dtor when it goes out of scope.

Hahaha. It is certainly not a fundamental misunderstanding.

All scope guards are built off of stack-allocated object lifetimes, specifically the scope guard itself. That is not "my opinion" or "my perspective", it is the reality. Try constructing a scope guard that isn't based off of the lifetime of an object on the stack. You can't do this, because the fact that it is tied to an object's lifespan is the point. One of the few points in C++'s favor is the fact that this relatively elegant mechanism can do so much.

> Scope guards using ctor/dtor mechanism is enough to implement all the policies like finally/defer etc. That was the point of the article.

You can kind of implement Go-style defer statements. Since Go-style defer statements run at the end of the current function rather than scope, you'd probably want a scope guard that you instantiate at the beginning of a function with a LIFO queue of std::functions that you can push to throughout the function. Seems like it works to me, not particularly elegant to use. But can you emulate `finally`? Again, no. FTA:

> In Java, Python, JavaScript, and C# an exception thrown from a finally block overwrites the original exception, and the original exception is lost. Update: Adam Rosenfield points out that Python 3.2 now saves the original exception as the context of the new exception, but it is still the new exception that is thrown.

> In C++, an exception thrown from a destructor triggers automatic program termination if the destructor is running due to an exception.

C++'s behavior here is actually one of the reasons why I don't like C++ exceptions very much, and have spent a lot of my time on -fno-exceptions (among many other reasons.)

> The article already points out the main issues (in both non-GC/GC languages) here but it is actually much more nuanced. While it is advised not to throw exceptions from a dtor C++ does give you std::uncaught_exceptions() which one can use for those special times when you must handle/throw exceptions in a dtor. More details at ...

Again, you can't really 100% emulate `finally` behavior using C++ destructors, because you can't throw a new exception from a destructor. `std::uncaught_exceptions()` really has nothing to do with this at all. Choosing not to throw in the destructor is not the same as being able to throw a new exception in the destructor and have it unwind from there. C++ just can't do the latter. You can typically do that in `finally`.

When Java introduced `finally` (I do not know if Java was the first language to have it, though it certainly must have been early) it was intended for just resource cleanup, and indeed, I imagine most uses of finally ever were just for closing files, one of the types of resources that you would want to be scoped like that.

However, in my experience the utility of `finally` has actually increased over time. Nowadays there's all kinds of random things you might want to do regardless of whether an exception is thrown. It's usually in the weeds a bit, like adjusting internal state to maintain consistency, but other times it is just handy to throw a log statement or something like that somewhere. Rather than break out a scope guard for these things, most of the time when I see this need arise in a C++ program, instead the logic is just duplicated both at the end of the `try` and `catch` blocks. I bet if I search long enough, I could find it in the wild on GitHub search.

Re: C++ says “We have try... finally at home”

#150

Calling arbitrary callbacks from a destructor is a bad idea. Sooner or later someone will violate the requirement about exceptions, and your program will be terminated immediately. So I'd only use this pattern in -fno-exceptions projects. In a similar vein, care must be taken when calling arbitrary callbacks while iterating a data structure - because the callback may well change the data structure being iterated (cla…

Throwing exceptions in destructors in C++ is fine, as long as your code is exception safe. What exactly are you referring to?

Throwing exceptions from destructors is "fine", except when the destructor is executed by stack unwinding (triggered by an earlier exception), in which case throwing will terminate the program with std::terminate.
Post reply on HN