6 ms·
> plenty of modern code written in languages like C++ (whose exception support is often eschewed) and Go gets by without [exceptions]. I would not consider C++
by deathanatos 10y ago
> plenty of modern code written in languages like C++ (whose exception support is often eschewed) and Go gets by without [exceptions].
I would not consider C++ code that eschews exceptions "modern". To eschew exceptions almost always entails giving up RAII, and if you give up RAII you've done yourself a huge disservice: you're now stuck manually managing memory and resources, and suffering all the bugs that come with that. While RAII does not outright prevent these bugs (which is one of the reasons I'm a huge fan of Rust), coding along its principles greatly reduces the risk that you will run into problems.
If you're wondering: giving up exceptions means a constructor has no way to signal failure, as the result of a ctor in C++ is always either an exception, or a constructed object. Most "no exceptions" C++ code I've seen opts to construct what I call "zombie objects"; internally, the object tracks that an error has occurred, and all uses of the real object must be first checked against that internal error flag to ensure the object isn't a zombie (or if it is, then error). The object is effectively a null pointer, and has all the trouble that entails.
(I also won't pretend that exceptions aren't without problems; in particular, the argument against them because you can't tell, at a particular point in a function's code, if an exception can occur, or where it would be caught, is completely valid, but no different in many popular languages, such as Python, C#, and in some ways, Java. However, I think the advantages of RAII — and exceptions — outweigh their disadvantages.)
> It just has to be written so that most functions manually propagate error codes they receive
When I've had to write C, I've found this to be exceptionally tedious to do correctly, and all too easy to ignore.
- ori_b 10y ago> To eschew exceptions almost always entails giving up RAII How so? RAII works just fine when you return errors.
- robotresearcher 10y agoHow about in constructors?
- reverius42 10y agoInit methods is the answer to that (but it potentially forces some additional complexity around initialized/not-initialized state into the other methods).
- p4wnc6 10y agoIn fact, I think managing this extra state, also dealing with complications that could arise from object slicing, were the major reasons given when Martin Sústrik argued he should have originally started out with ZeroMQ in C and not in C++. < http://250bpm.com/blog:4 http://250bpm.com/blog:4 >
- deathanatos 10y agoAs I stated in my original post, these have the same classes of problems as null pointers: you have "constructed" an object whose internal state in invalid; a instance of Foo is either a Foo that init'd successfully or not-a-Foo (that failed to init/isn't initialized; a "zombie object") just as a pointer is either a pointer-to-something or a pointer-to-nothing. This is why you say "it potentially forces some additional complexity around initialized/not-initialized state into the other methods. At least in C++ (and many other OO languages), I find the downsides of having these Jekyll and Hyde classes outweigh any benefits that may come of ignoring exceptions.
- deleted 10y ago[deleted]
- telotortium 10y agoIf a constructor can fail, don't make that constructor public. Instead, add a trivial constructor to allocate an empty instance of the object, then make your possibly-failing constructor a factory function that takes whatever arguments you would pass to the constructor and returns an error code. The object being "constructed" should then be passed by reference as one of the arguments to the factory function and filled in. (If you're writing this in Rust, you have better options, since you can explicitly take ownership of some of the factory function arguments and avoid the need to allocate a trivial instance of the object by returning it as one of the variants of the sum type Result<T, E>.) Note: I don't actually know C++ very well. It's possible there's a more idiomatic way to accomplish this. In particular, another wrapper class could be created that owns an instance of the trivially-constructed instance mentioned above. The factory function could then take an instance of the wrapper class and then modify and return an instance of the class you actually want to obtain.
- deleted 10y ago[deleted]
- kabdib 10y agoI've not seen a large production C++ code base that did anything with exceptions other than to catch them, record some debug information and restart. This is especially true when third party code is involved. C++ is hard enough to get right without exceptions; dealing with failure in bodies of code that you didn't write and maybe don't have control over (or even source code of) is death on wheels. If that means the code I'm working on isn't modern . . . I think that's fine. But the first volume (of three?) of Herb Sutter's Exceptional C++ should be enough to convince anyone that dealing with exceptions "correctly" is time better spent eschewing exceptions and restructuring your code so you can do worthwhile hard things.
- deathanatos 10y ago> C++ is hard enough to get right without exceptions In what manner? If you use exceptions, you must (IMO) use RAII, or yes, it will be painful. Every example of "painful C++" I've seen involved someone ignoring that, and that is fighting the language. I understand that's a bit of a strawman argument, but you didn't elaborate enough in your post to debate it. If you are using RAII, exceptions shouldn't be terribly hard to "get right"; an exception propagating up the stack will free resources as it goes. (This is the same behavior, again, as numerous other languages; except in C++, I have the benefit of RAII, in others, I generally do not.) > dealing with failure in bodies of code that you didn't write and maybe don't have control over (or even source code of) is death on wheels. It's peculiar that I only ever hear this argument against C++, when Python, C#, and to an extent Java¹ work in the same manner. Were you writing a C++ webserver, for example, I expect that you would do exactly what my current job's Python webserver does: if an uncaught exception makes it to the main request handler, it is caught in a catch-all, duly logged, and a 500 served in response. This is appropriate both in Python and C++ IMO. (Whereas in the hypothetical C++ side, I gain the benefits of static typing and RAII that I can never have in Python.) In my day-to-day Python, we quite often learn about uncaught exceptions — from third-party code that I didn't write and maybe don't have control over — in production; sometimes, we will add a handler for that particular exception where appropriate (it was a bug) or fix the underlying symptom (the exception indicates a larger problem, and allowing it to propagate to a good catch-all point was appropriate, as was 500'ing the request). But again, "death on wheels" is not really something I can provide worthwhile argument against. Perhaps where third-party code has caused pain (at least, for me) is when a very low level (say, socket error) propagates through the stack; there might not be a good way to catch these beyond "catch everything", but again, that's not unique to C++. The biggest problem I've had with these is that they lack context: what caused this socket error? Or, if some library catches & re-throws its own exception class, too often do they discard the inner error, and I lack the root cause (a different lack of context). But you see this in Python as well. Error codes are perhaps the worst, as they will quite often force you to lose context; do you expose error codes for all of your inner error conditions? (And theirs, transitively?). (I've never seen a non-exception C++ or a C codebase use anything aside from error codes as a substitute.)
- cmrdporcupine 10y agoI don't think it's a secret that Google's C++ code base (very large and growing) does not use exceptions. And since starting there I have not missed them. Exceptions make execution flow hard to reason about. And RAII is most certainly still a thing without exceptions. There's lots of other ways to unwind the stack.
- Ace17 10y agoGoogle is an example that writing production code that doesn't use exceptions is possible, not that this is the right thing to do. They said that their decision was primarily legacy-motivated (in short, they were fearing that introducing exceptions in a non-RAII codebase would lead us to a long period of instability). I'll add that in some situations (e.g when freeing resources) the control flow gets very convoluted, and you don't want to deal with directly. See: Andrei Alexandrescu - Declarative Control Flow ( https://www.youtube.com/watch?v=WjTrfoiB0MQ https://www.youtube.com/watch?v=WjTrfoiB0MQ )
- niccaluim 10y ago>> It just has to be written so that most functions manually propagate error codes they receive > When I've had to write C, I've found this to be exceptionally tedious to do correctly, and all too easy to ignore. Indeed. I write Go all day and I type "if err != nil { return nil, err }" so often I could scream.
- pjc50 10y agoThey should probably add a keyword for that phrase.
- duaneb 10y agoHow does RAII deal with reference loops? It is trivial to create a loop of owned pointers in memory that never decrement, and it isn't that unusual of a pattern not to be a concern. Just curious; I used a combination of strong/weak reference counting when I dealt with this. It wasn't fast, but it did appear to be less prone to implicit leaking.
- lukewink 10y agoOne method is using weak_ptr.
- flohofwoe 10y agoRAII (or rather the only useful part of the idea: that the destructor is called when an object goes out of scope) without exceptions is totally fine if you're not too dogmatic about initialising everything (especially not things that can go wrong) in the constructor but instead have one or more separate init methods with a return value. The only important part is to cleanup everything in the destructor. This is all IMHO of course, but I've been writing C++ code for 20 years just fine without ever thinking 'gee it would be nice to use exceptions here'.
- yoklov 10y agoStrongly agree. RAII without exceptions is fine. Exceptions are deeply flawed for code understandability, and anything you get by doing work that can fail in the ctor is mot worth it.
- lmm 10y agoBeing able to construct without initialising is the flipside of being able to descope without deinitialising, and introduces much the same problems. It makes it much harder to use immutability (admittedly something that's hard in C++ in any case). It makes sharing objects between threads much harder to reason about, as you need to make sure init a) only happens once, but more importantly b) the effects are visible to any other thread before it uses the object. How much code have you written with exceptions? A lot of features you don't miss if you don't use them, but once you're used to use them it's very frustrating to live without them. Back when I wrote C I never thought "gee it would be nice to use an anonymous function here", but now that I'm used to them it's very frustrating to not have them.
- richardwhiuk 10y agoYou can perfectly well have classes which have different levels of initialization and clean them all up in the destructor... RAII is generally useful without exceptions.
- flohofwoe 10y agoAdmittedly I haven't written much code with exceptions, only when using C++ frameworks that heavily rely on them, but this was always for tools where productivity was more important than performance, my actual background is game development. I have stumbled more recently over another case where the constructor/destructor paradigm in C++ is not ideal, since in some cases it 'encourages' constructing objects on the heap, just for the reason that allocation and initialisation are the same operation, I haven't thought this completely through yet, but bear with me: Basically I'm trying to write code in a way that minimizes heap allocations. In the extreme case I have just one big 'application object', and all other objects are embedded in this application object. For variable number of objects, pools/arrays with the max capacity are embedded. If taken to the extreme, this model only does a single allocation at application startup (or it could even live on the stack). However, there are systems which need to be initialized later, and in a specific order (for instance rendering, audio, input, etc...). The objects for these systems have already been created and had their constructor run, but they are not ready for use. That's where I need separate initialisation steps, unless I want to put those objects back on the heap. And in reverse, I need to teardown graphics, audio, input before the big application object is destroyed (and the destructors of the embedded objects are called). Now I don't think that this extreme case of trying to minimize dynamic allocations down to 1 is particularly useful in real world code, it's more of a though experiment, but in this case, the good ol'e C way of separating allocation/deallocation from initialization/teardown is actually more useful. Unfortunately I have stumbled over C++ frameworks which basically require to create all objects on the heap, which in my opinion is a design fault. A framework should not dictate to its users whether objects are created on the stack, on the heap, or are embedded in other objects.
- nshepperd 10y agoEh? RAII works perfectly fine without exceptions. Either - You write a constructor that cannot fail for RAII types that cannot fail, or - return some kind of option type from your BuildRAII() function, and handle the failure case however you want.