4 ms·
These objections seem pretty easily addressed: 1. Don’t use exceptions 2. If you have constructors that can fail, use heap objects and factory functions.
by iainmerrick 8y ago
These objections seem pretty easily addressed:
1. Don’t use exceptions
2. If you have constructors that can fail, use heap objects and factory functions.
- flukus 8y agoThose solutions have other consequences because of how everything interacts. No exceptions means no std and you have to be very careful about what other libraries might do. Likewise it makes the consuming code more complicated, they now have to deal with return codes and exceptions. You can't really just avoid language features like that.
- slavik81 8y agoI've been writing C++ for ten years and I can count the number of try/catch blocks I've written on one hand. The only exception I occasionally want to catch is std::bad_alloc. Other exceptions are basically the equivalent of an assert or a segfault. They will never be thrown unless there is an error in my program.
- jcranmer 8y agoMany large C++ projects--LLVM, Chromium, Firefox--disable C++ exception handling. They still use the standard C++ library. Exceptions in the standard library boil down to a) passing through user's exceptions, b) failing memory allocation (note that some systems, such as Linux, won't fail a memory allocation but instead crash you when you try to use it), and c) throwing exceptions when you exceed the bounds of an array or the like (~= Java's RuntimeException). With exception handling turned off, class (a) doesn't exist and classes (b) and (c) turn into hard program crashes, which is almost invariably what you want to do for these errors anyways. Despite the fact that C++ has been my main programming language for a decade, I don't actually know how to write catch clauses correctly in C++, unlike Java, Python, and JS. That's how easy it is to avoid C++ exceptions.
- wahern 8y ago> some systems, such as Linux, won't fail a memory allocation but instead crash you when you try to use it False. 1) You can disable overcommit, and there are many of us that do this as a matter of course on all our Linux servers. 2) malloc can fail because of process limits (e.g. setrlimit or cgroups). I don't program in C++, but I do use RAII-like patterns in C. By that I mean that when I create and initialize objects, all the necessary internal resources--particularly those that rely on dynamic allocation--are also created and initialized in the same routine. That means most places where memory allocation can fail are grouped closely together into a handful of initialization routines, and the places where allocation failure results in unwinding of a logical task are even fewer. (While C doesn't automatically release resources, following an RAII-like pattern means deallocations are just as structured and orderly as allocations.) I can understand bailing on allocation failure in scripting languages--not only is there much more dynamic allocation taking place, but allocation happens piece-meal all over the program, and often in a very unstructured manner (strings of variably length generated all over the place). Furthermore, often script execution occurs in a context that that can be isolated and therefore unwound at that interface boundary--i.e. a C- or C++-based service executing a script to handle a transaction. But in languages like C++ and Rust, especially for infrastructure software and libraries, it's a sin IMO. These are languages intended for use in situations where you can carefully structure your code, they make it trivial to minimize the number of allocations (because POD-oriented), and they permit one to group and isolate allocations in ways (e.g. RAII) that make it practical (if not trivial) to unwind and recover program state.[1] But why even bother? 1) Because these languages are often used in situations where failure matters. A core piece of system software that fails on malloc is a core piece of system software that is unreliable, and programs that rely on you can behave in unpredictable and even insecure ways. 1.a) Go authors take the view that reliability comes from running multiple instances in the cloud. Yes, that's one way, but it's not the only way, not always an option, and in any event anybody with enough experience dealing with "glitches" in cloud services understands that at least in terms of QoS there's no substitute for well-written, reliable service instances. 1.b) Security. It's often trivial to create memory pressure on a box. OOM killers are notorious for killing random processes, and even without overcommit the order of allocations across all processes is non-deterministic. Therefore, not handling OOM gives attackers a way to selectively kill critical services on a box. Disruption of core services can tickle bugs across the system. 2) Overcommit is an evil all its own. It leads to the equivalent of buffer bloat. Overcommit makes it difficult if not impossible to respond to memory resource backpressure. This leads to reliance on magic numbers and hand-tweaking various sorts of internal limits of programs. We've come full circle to the 1980s where enterprise software no longer scales automatically (which for a brief period in the late 1990s early 2000s was a real goal, often achieved), but instead requires lots of knob turning to become minimally reliable. Nobody questions this anymore. (Ironically, Linux helped lead the way to knob-free enterprise operating systems by making kernel data structures like process tables and file descriptor tables fully dynamic rather than statically sized at compile or boot time, so the kernel automatically scaled from PCs to huge servers without requiring a sysadmin to tweak this stuff. Notably, Linux doesn't just crash if it can't allocate a new file descriptor entry, nor do most programs immediately crash when open fails. Even more ironically, overcommit on Linux was originally justified to support old software which preallocated huge buffers; but such software was written in an era where sysadmins were expected to tailor hardware and system resources to the software application. Overcommit has had perpetuated the original sin.) Not all software needs to handle OOM. Not event all C, C++, or Rust components. But for infrastructure software OOM should be handled no different than file or network errors--subcomponents should be capable of maintaining consistent state and bubbling the error upward to let the larger application make the decision. And if you're not writing critical infrastructure software, why are you using these languages anyhow?[2] If a language or framework doesn't permit components to do this, then they're fundamentally flawed, at least in so far as they're used (directly or indirectly) for critical services. You wouldn't expect the Linux kernel to panic on OOM (although some poorly written parts will, causing no end up headaches). You wouldn't expect libc to panic on OOM. There's no categorical dividing line beyond which developers are excused from caring about such issues. [1] Granted, Rust is a little more difficult as it's hostile to allocation-free linked-lists and trees, such as in BSD <sys/queue.h> and <sys/tree.h>. Hash tables require [potential] insertion-time allocation. Still, it's not insurmountable. Many forms of dynamic allocation that can't be rolled into a task-specific initialization phase, such as buffers and caches, are colocated with other operations, like file operations, which already must handle spurious runtime failures, and so the failure paths can be shared. [2] Maybe for performance? Performance critical tasks are easily farmed out to libraries, libraries are particularly suited to handling OOM gracefully by unwinding state up to the interface boundary.
- topspin 8y ago> You can't really just avoid language features like that. As jcranmer points out, forgoing C++ exceptions is literally written policy for the browser you are likely reading this with right now. And no, you don't give up std. C++ exceptions have been a misfeature since inception.
- Koshkin 8y agoThrowing an exception is the only sensible way for a constructor to signal a failure.
- speedplane 8y agoHow about this: class Foo { void Foo() { this.error = true/false; } bool constructed() { return this.error } }
- dymk 8y agoThis is how you get the equivalent of `if err != nil { return err; }` everywhere in your codebase. We've already come to the conclusion that this pattern is terrible. What you really need is to separate object allocation from construction. But then you're adopting an object model that C++ just doesn't support.
- jcelerier 8y agoIt sucks versus Foo() { if(...) throw whatever; }. It needs more code (check for constructed() every time, and if you forget to checking it you've introduced a bug), and is less performant (since you will have one additional branch in the "if(constructed())" mandatory check.
- Koshkin 8y agoThe ‘error’ member variable would take an additional space in the object that otherwise could be as small as a byte. C++ is carefully designed to avoid unnecessary overhead.
- 8y ago
- iainmerrick 8y agoAs others have said, you can perfectly well use (most of) std without exceptions. Turning off exceptions is very common among C++ teams, depending on the industry they’re in. In general, you can (and should) pick a subset of language features. C++ is infamous for that.