3 ms·
I think there's a lot of merit to this post, but it really shows its age. Modern C++ typically suffers from only the last point raised - exceptions during destr
by laurowyn 6y ago
I think there's a lot of merit to this post, but it really shows its age. Modern C++ typically suffers from only the last point raised - exceptions during destruction, but that goes against the all good C++ practices.
Overall, the author's approach to exception use is flawed. It's bad form to use exceptions for code flow purposes, as per their example. If you throw an exception in the same block that catches the exception, you've wasted a whole lot of time doing something an IF-statement could solve trivially.
All projects that I have worked on that use C++ has declared that exceptions should be used in exceptional circumstances only - that is, when you cannot handle the error in the current code block and the function interface does not support returning enough information to the caller to detail what went wrong. An exception is more than just throwing your hands in the air and saying "I can't do that" - it has context, purpose, history. Getting rid of exceptions breaks some of the fundamental design concepts of the language.
2-step initialisation is the only way around errors during construction whilst forbidding exceptions. But 2-step init breaks a fundamental rule of RAII - the constructor acquires the resource and establishes all class invariants or throws an exception if that cannot be done. If the constructor is not allowed to throw exceptions, as per the language's standardised interface for constructors, then my library or application needs to be modified to support whatever process a third party library has defined as appropriate. There is a wide surface for bugs to creep in, let alone costing me time, money and effort in supporting whatever interface they've come up with.
If an object has been constructed, it should be in a valid state unless an exception has been thrown, in which case I'm told what went wrong. If I can fix it before the stack is fully unwound, I can save the day. but if there's nothing I can do, the exception has to roll to the top as that's the only other option. That then begs the question; should I catch all exceptions at the top level and prevent crashing, or should I crash and allow whatever system I'm running under to restart me? That depends on the project, but typically I'd let it go. systemd should bring me back, docker should restart me, kubernetes should restart my pod, etc etc. If I focus on what is within my control, and delegate everything else, the system will be cleaner and much more maintainable.
I've never come across a situation where exceptions during destruction is a problem, but am very interested in any examples. C++ standards define that you _can_ throw exceptions, but you shouldn't for the exact reason raised in the article - the process will be terminated as there's nothing else that can be done. If there aren't any destructors containing the throw keyword, it's not likely to throw an exception - OOM or other system exceptions are still possible, but why are you allocating memory in a destructor? destructors just need to release resources and clear down the object, it shouldn't be requesting more resources. Thinking about saving the object state before exit? wrong place to do it.
- andi999 6y agoA problem is if you use RAII for other resources than memory, for example files. You want to put the close function in the destructor then, but close can throw an exception. http://www.cplusplus.com/reference/fstream/ofstream/close/ http://www.cplusplus.com/reference/fstream/ofstream/close/ (Of course the solution is easy, you wrap close in a try catch block, but as alwaya all the solutions are easy in c++ but this is one of the surprising corners, which might be done more often wrong then not)
- laurowyn 6y agoInteresting corner case that I can't say I've ever seen before. fstream is a bit of a mess. Even the documentation for close is a bit contradictory. Its exception safety states that an exception is caught and rethrown after closing the file if one is thrown by an internal operation. That to me sounds like the actual resource being managed will be closed (assuming the fd is valid), but any data may not be flushed out of the write buffer. sounds to me like iostream needs some serious attention in future specs, but that's unlikely to happen unfortunately. Creating new toys > fixing old ones.
- gpderetta 6y agoFstream is hardly a paragon of good design. Then again there is no reason to call close in the destructor as fstream destructor itself will call close. If you care about reliability and want to make sure your stream is flushed, you probably want an explicit commit interface and reserve the destructor for rollback (which necessarily shouldn't possibly fail).
- deleted 6y ago[deleted]
- jschwartzi 6y agoAnother way to support RAII without throwing exceptions is to somehow make the error case an explicit configuration of the class with well-defined behavior. For example, in a "File" class with no filedescriptor you could return a defined "error" from any function calls which would require a filedescriptor. Receiving such a return value or return structure would then signal that the File class is not in a working state.