Timeline for Why is there no 'finally' construct in C++?
Current License: CC BY-SA 3.0
20 events
when toggle format | what | by | license | comment | |
---|---|---|---|---|---|
Sep 3, 2015 at 22:29 | review | Low quality posts | |||
Sep 4, 2015 at 13:56 | |||||
Sep 3, 2015 at 20:06 | comment | added | Vector | This is too involved for comments. Post a question about it: How would you handle this scenario in C++ using the RAII model... it doesn't seem to work... Again, you should direct your comments : type @ and name of the member you're talking to at the beginning of your comment. When comments are on your own post, you get notified of everything, but others don't unless you direct a comment to them. | |
Sep 3, 2015 at 13:30 | comment | added | MadScientist |
RAII in C++ is based on destructors. Hence, it's not possible to use RAII to implement any operation in C++ which might fail in a way that cannot be completely handled within the destructor. If you take my original example and replace complex_cleanup() with close() where the close operation can fail, and where detecting that the close operation failed is important and can't be handled within the destructor, then RAII cannot be used. In short, there is no way to translate my original example safely into a model that uses RAII. That's my whole point.
|
|
Sep 3, 2015 at 13:24 | comment | added | MadScientist | You seem to be assuming I don't know what RAII is and as a result are not reading my comments carefully. I'm very, very familiar with RAII: you don't have to teach me how to use it. I fully understand it's based on stack objects. I will try again. A destructor has no return type. A destructor cannot safely throw an exception. Hence a destructor must always "succeed" and cannot indicate an error condition. Hence you cannot use a destructor to perform any operation which might fail in a way that must be published (the destructor must handle all error conditions internally). | |
Sep 3, 2015 at 0:58 | comment | added | Vector | Destructors can do that. Destructor is called when the function exits. If your file object is on the stack, an exception clears the stack and destructors are called. see Are destructors called after a throw in C++?. BTW, incorrect use of pointers leads to more than just "memory leaks", but general problems with resource allocation, like open file handles.Key to RAII is initializing objects on the stack not on the free store with pointers.Read carefully answers here and there.(also direct your comments) | |
Sep 2, 2015 at 18:21 | comment | added | MadScientist | I'm sorry but memory management is the least interesting use of RAII: you can't "fail" to free memory, so if pointers is all you think about you're missing most of it. Consider a file. You want to use RAII to be sure the file is closed when a function exits via either normal return or exception. You create an object representing the file whose destructor closes the file. RAII FTW! But what if the close of the file fails, and you must learn about that? Destructors can't do this in C++. RAII fail. | |
Sep 1, 2015 at 23:19 | comment | added | Vector | If you find yourself with the need to raise exceptions in destructors, you're doing something wrong - probably using pointers in other places when they're not necessary. | |
Sep 1, 2015 at 4:18 | comment | added | MadScientist | I agree with those nice features of RAII and I'd use it more, except for one problem: exceptions. As I said above, the failure to manage exceptions means RAII cannot be used in many situations. I don't know about your code but regardless of how complex or not complex your cleanup code is, sometimes it will fail and when it fails, you need to throw an exception. And in that case you cannot use RAII. It's not the fault of RAII as a concept. It's the fault of C++'s inability to handle this common requirement in its RAII implementation. | |
Aug 31, 2015 at 23:17 | comment | added | Vector |
(cont..) Many developers seem to shy away from instantiation of classes on the stack because more thought and careful design is required to do it correctly than simply using pointers with new and then relying on constructs like complexCleanup() and explicitly called destructors. That doesn't mean it's the cleanest, most efficient approach to development.
|
|
Aug 31, 2015 at 23:09 | comment | added | Vector |
finally is necessary in a language like Delphi, which has no automated garbage collection (it really does, but that's not the point here) yet does not allow instantiation of classes on the stack, leaving you to manage their cleanup. C++ supports instantiation of classes on the stack, precluding the need for finally and complexCleanup() .
|
|
Aug 31, 2015 at 23:04 | comment | added | Vector |
Using RAII properly, you should not really be calling complex_cleanup() functions. The point of RAII is that the stack is your best cleanup tool - your code does not explicitly handle resource cleanup. BTW, the answer is not that RAII is a superset of finally - the answer is that finally is an unnecessary, clumsy construct that leads to poorly structured code and improper allocation of resources as compared to RAII. I asked this question two years ago, read Stroustrup on RAII after getting the answer, and I have since written a lot of fast,efficient code that has no need for finally .
|
|
Aug 31, 2015 at 15:59 | comment | added | Josh Kelley | Let us continue this discussion in chat. | |
Aug 31, 2015 at 15:52 | comment | added | MadScientist |
Regarding finally : yes I know exceptions don't nest in C++, but throwing from inside catch is well-defined and does not cause your program to terminate. Certainly a throw from finally would work the same way, and not behave like a throw from a destructor. In cleanup() I want to throw, I want to see the exception, but I don't want to terminate my program. Thus I cannot call it from a destructor, thus I cannot use it with RAII, and thus RAII, while cool, cannot replace finally . Nested try/catch can't help. Destructors are a round peg for an oblong hole.
|
|
Aug 31, 2015 at 15:28 | comment | added | MadScientist |
Yes, it is rare that someone writes a destructor like A::~A() that throws. But the entire point of RAII is that you take generic cleanup code, that is not normally part of a destructor, and put it into a destructor in order to ensure it's invoked (see for example SCOPE_EXIT in ScopeGuard). That generic cleanup code, if it's at all complex or interesting, will want to throw: it's not rare at all. It's quite possible you're only using RAII for simple things like ensuring heap cleanup, locking, etc. and it works perfectly for that. But it is not general-purpose.
|
|
Aug 31, 2015 at 12:42 | comment | added | Josh Kelley | I understand your point - there are some legitimate issues with destructors that might throw - but those are rare. Saying that RAII + exceptions has unresolved issues or that RAII isn't a general-purpose solution simply doesn't match the experience of most C++ developers. | |
Aug 31, 2015 at 12:41 | comment | added | Josh Kelley |
But your first example is willing to lose exceptions. If complex_cleanup throws, then the original A /B /... exception is lost. From what I understand, languages like Java and C# work the same way with finally - if finally throws, then the original exception is lost. For better or worse, C++ has decided that it refuses to lose exceptions - it would rather call terminate - and, even if it added finally , finally would probably work the same way, so it wouldn't solve your problem. Like I said, if you're willing to lose exceptions, a nested try/catch block should work.
|
|
Aug 31, 2015 at 4:49 | comment | added | MadScientist |
I want to use RAII to get identical behavior as the first example, more safely. A throw in a putative finally block would clearly work the same as a throw in a catch block WRT in-flight exceptions--not call std::terminate . The question is "why no finally in C++?" and the answers all say "you don't need it... RAII FTW!" My point is that yes, RAII is fine for simple cases like memory management, but until the issue of exceptions is solved it requires too much thought/overhead/concern/re-design to be a general-purpose solution.
|
|
Aug 31, 2015 at 1:26 | comment | added | Josh Kelley |
In your second example, if complex_cleanup can throw, then you could have a case where two uncaught exceptions are in flight at once, just as you would with RAII / destructors, and C++ refuses to allow this. If you want the original exception to be seen, then complex_cleanup should prevent any exceptions, just as it would with RAII / destructors. If you want complex_cleanup 's exception to be seen, then I think you can use nested try/catch blocks - although this is a tangent and hard to fit into a comment, so it's worth a separate question.
|
|
Aug 30, 2015 at 22:15 | review | First posts | |||
Aug 30, 2015 at 22:32 | |||||
Aug 30, 2015 at 22:13 | history | answered | MadScientist | CC BY-SA 3.0 |