4 ms·
I am very confused. Some of these ramblings are just plain wrong, like > But exceptions come at a performance cost. No they don't. An exception that isn't th
by qayxc 5y ago
I am very confused.
Some of these ramblings are just plain wrong, like
> But exceptions come at a performance cost.
No they don't. An exception that isn't thrown costs nothing.
Also
> Let’s say I want to know if a constructor failed. I have two options, one is to pass
in an in-out parameter, the other is to the throw an exception.
Using a factory function instead is a third option that avoids this. Besides, this is a false dichotomy to begin with - it's perfectly acceptable to simply record the validity as part of the object's state and provide access to that (especially if the state established in the ctor isn't an invariant). So there's more than just two options.
Then there's more confusing arguments like the custom FILE wrapper. So instead of just using std::unique_ptr<std::FILE, decltype(&close_file)>, i.e. the idiomatic solution for the past decade that the author claims to be after, they blame their C++03-style on the language? I don't understand.
Fortunately they don't provide any concrete examples of "features bleeding into each other", since I suspect that would only further illustrate their lack of
understanding and knowledge gaps...
They end with
> I’m not in the business of writing the perfect program that satisfies an arbitrary standard. I’m in the business of making stuff with tools. [...]
Well great, then don't use C++ if you're not productive with it. Yes C++ is a complex language. A big portion of its complexity can be avoided by simply not using features one doesn't need (or understand) or by limiting oneself to a reasonable subset of said features.
The author seems to look for something like Go instead. C++ wasn't exactly "designed" and is the result of a steady evolution that kept some legacy baggage and bad earlier decisions around.
- ghosty141 5y agoTo add to "Let’s say I want to know if a constructor failed." Constructors are simply not meant to fail. You should never do something like io in a constructor. For everything more complicated than setting some fields a factory is better suited.
- nmilo 5y agoWhy? Isn't one of the ideas of C++ that every constructed object is in a valid state? Even the STL has constructors that can fail, and it does it through member variables/flags, which doesn't seem like a very good solution: std::fstream s(filename); if (!s.is_open()) { std::cout << "failed to open " << filename << '\n'; } No one should ever have to use factories, they really suck. Too much boilerplate, too much code duplication.
- jcelerier 5y agohow do you do non-trivial dynamically-loaded plug-ins without factories ?
- dataflow 5y ago> Constructors are simply not meant to fail. You should never do something like io in a constructor. For everything more complicated than setting some fields a factory is better suited. How would you reconcile that with the fact that std::vector<T>::vector() is explicitly permitted to fail? [1] Do you construct your std::vector<T> objects with factories too? > Exceptions: Calls to Allocator::allocate may throw. [1] https://en.cppreference.com/w/cpp/container/vector/vector https://en.cppreference.com/w/cpp/container/vector/vector
- Const-me 5y ago> Do you construct your std::vector<T> objects with factories too? I usually do it like that: try { vec.resize( wantedSize ); } catch( const std::bad_alloc& ) { return E_OUTOFMEMORY; }
- dataflow 5y agoYeah but that's completely avoiding my point. You're claiming constructors aren't supposed to throw, but I'm pointing out the standard explicitly lets vector's constructors fail, and there's no other mechanism to handle that failure than by catching the exception (it's literally designed that way). It's fine if you prefer not to use those constructors in the first place, but that's kind of beside my point about what constructors are "supposed" to do.
- Const-me 5y ago> but I'm pointing out the standard explicitly lets vector's constructors fail Yeah, but that thing has more than one constructor. Since C++/17, the default constructor of std::vector which doesn’t allocate any storage is usually marked as noexcept, i.e. the standard does not allow it to throw anything. This feature allows to make classes which contain vectors yet don’t throw exceptions from constructors. P.S. The move constructor of std::vector is marked with noexcept as well, i.e. it’s even possible to construct non-empty vectors without any exceptions.
- knorker 5y ago> The author seems to look for something like Go instead But in Go you also have to write exception safe code, something the author complained about. Before someone says "but you're not supposed to throw or catch in Go": the std http server swallows exceptions thrown in handlers, and fmt.Print does for String callbacks too. And before someone says Go doesn't have exceptions: It does, 100%, in all but name. I actually find C++ to be way more designed, though through, and consistent than Go. That's not to say that Go isn't a vastly simpler language. It is. And C++ has way more "just because you can doesn't mean you should".
- nmilo 5y ago> And before someone says Go doesn't have exceptions: It does, 100%, in all but name. But here the name actually matters. They're called "panic"s, not exceptions. When you panic, you signal a fatal error that should ideally terminate the program. You shouldn't care about closing files or freeing memory, because well-written Go does not panic, and if it does, execution will end very soon. A panic in a String() method is just bad code, don't do it. Meanwhile in C++ you can reasonable expect an exception anywhere. Bad arguments, invalid states, IO failures, and other errors that should be expected in the execution of a program all throw exceptions in idiomatic C++.
- pjmlp 5y agoExcept you can catch panics, and not every Go package is such example of panic purity.
- knorker 5y ago> But here the name actually matters. They're called "panic"s, not exceptions. But crucially that doesn't at all affect what they do, how they do it, or what it means for programmers needing to write code. Even if you never write "panic", or never write "recover", your code still needs to be exception safe. > When you panic, you signal a fatal error that should ideally terminate the program The "should ideally" is the flaw in this plan. If your code could EVER be called within an HTTP handler, or from a String() function, then you NEED to write it exception safe. This code is a time bomb: […] foo.Lock() something() foo.Unlock() […] To function correctly it must be: […] func() { foo.Lock() defer foo.Unlock() something() }() […] > You shouldn't care about closing files or freeing memory But I do care about deleting tempfiles, unlocking mutexes, closing channels, etc…, because you do need to do that. And this is the Go authors themselves showing off panics, saying "there's a bug" if the file isn't closed: https://go.dev/blog/defer-panic-and-recover https://go.dev/blog/defer-panic-and-recover Go authors explicitly say you should NOT count on finalizers for correct behaviour. So no, close your files. > well-written Go does not panic But sometimes it does. And if your code is not exception safe then you turn a safe problem into, essentially, undefined behaviour. > and if it does, execution will end very soon. Nope, not if panicing in an HTTP handler. If these two HUGE examples I've given you didn't exist then I would likely agree with you. But I'm going to declare knorker's law: Every piece of code ever written will at some point be called from an HTTP handler. > A panic in a String() method is just bad code, don't do it. That's too much "just don't write bugs". Modern languages are supposed to be safe, no? > in C++ you can reasonable expect an exception anywhere Yes, just like in Go you never know in what context your code will run in the future. Which means that in both Go and C++ you need to write exception-safe code even if you never throw or catch exceptions. > [C++ exception for] IO failures […in idiomatic C++] Oh? The standard library doesn't do this, right? What makes you say that's idiomatic C++? Idiomatic Java, sure, but Java is just crazy about exceptions for everything. > [C++ exceptions for] invalid states [… in idiomatic C++] Idiomatic Go also calls for panic for the "can't happen" case. > other errors that should be expected in the execution of a program all throw exceptions in idiomatic C++. What makes you say this is idiomatic C++? I've seen Go code that does it too, but that doesn't make it idiomatic Go. Does the C++ standard library do this? The closest thing I can think of is std::bad_alloc on OOM, but then again Go panics then too.
- Rusky 5y ago> An exception that isn't thrown costs nothing. It costs binary size for unwinding tables, and in some cases it is an optimization barrier.
- jcelerier 5y agoand yet when benchmarking, exceptions are often faster. cross-posting a comment I just made on r/programming: https://www.reddit.com/r/programming/comments/sz86fl/comment/hy5iprs https://www.reddit.com/r/programming/comments/sz86fl/comment...
- UncleMeat 5y agoC++ exceptions aren't totally zero overhead. For an obvious example, you pay in code size. I do agree with the overall analysis, though.
- nyanpasu64 5y agoUsing a function pointer as a unique_ptr's deleter type, and passing it in at runtime, makes the unique_ptr fat (two pointers large). The "idiomatic solution for the past decade" is a lot more complicated than you make it sound. To avoid this overhead, I prefer passing in a default-constructible type with an operator() deleter function, and not passing in a value into the unique_ptr constructor. A neat party trick is to use the decltype of a lambda as such a type (https://old.reddit.com/r/cpp/comments/rlvsq0/does_anybody_really_use_deleters/hpmg0k6/ https://old.reddit.com/r/cpp/comments/rlvsq0/does_anybody_re...).
- deleted 5y ago[deleted]
- ggreg84 5y ago> No they don't. An exception that isn't thrown costs nothing. Wait whaaat? When every expression can throw anything, the compiler and the user both loose the ability to reason about code locally. They change any function from returning one value of one type, to returning any value of any type. This: - has a performance cost: it breaks lots of compiler optimizations that require knowing where and what can functions return. - has a compile-time cost: compiler needs to generate code for landing pads all over the place, this code must be optimized, etc. - has a code-size cost: the landing pads must be generated in the binary, - has a usability cost: users can't reason about their programs if they can't reason about where functions can return and what they can return - has a teachability cost: users have to be taught _a lot_ about how to use exceptions properly because of their cost. Herb Sutter has a whole damn series of books, a series of damn books, about how to use exceptions properly and avoid their pitfalls. When you need a series of books to explain 1 single feature of your programming language, that feature is _wrong_. And writing and reading these books has a huge cost. Exceptions are as free as Google or Facebook. You can use them for free as long as you gift them your soul so that they can sell it for profit.
- jcelerier 5y agoit's obvious that OP meant a performance cost - as generally when you program in C++ that's the n°1 metric. And for that one, exceptions as they are today are often faster than the equivalent error-code-based code: https://nibblestew.blogspot.com/2017/01/measuring-execution-performance-of-c.html https://nibblestew.blogspot.com/2017/01/measuring-execution-... Of course if we can get even faster exceptions such as the throwing values model, by all means let's do it... i'm entirely fine with recompiling literally every software / library I use if that can get me half a % more performance
- ggreg84 5y ago> And for that one, exceptions as they are today are often faster than the equivalent error-code-based code: https://nibblestew.blogspot.com/2017/01/measuring-execution- https://nibblestew.blogspot.com/2017/01/measuring-execution-... The example on that blog post is not representative of how error codes are used. On all programming languages, C included, but also Rust, functions return a completion value that's either success, or error (and when its error, it includes which error it is, and the error state, etc.). int foo(); switch (foo()) case 0: break; // success case 1: /* error case 1 */ case 2: /* error case 2 */ ... Instead, the blog post has the function return some value in registers that is not an error, and passes the function an ErrorState* as an argument, that the function first sets via memory, and then the handler reads, also via memory. That this is slow is not rocket science, which is why nobody does this. I get why the author has to do it. If they were to do it right, error codes would perform significantly better than exceptions. Since returning the error or success is "free" (the function has to return anyways, and up to 2 registers are reserved by the ABI anyways). Somebody mentions this in the comments, were as far as fixing it and sending a PR (https://github.com/jpakkane/excspeed/pull/1 https://github.com/jpakkane/excspeed/pull/1) but the author complains that this is not how GLib does error codes, so they don't care. So yeah, if you do C error codes like GLib does, which is essentially a poor man's emulation of exceptions, then sure, exceptions are faster than doing error codes wrong. Most people using C do it right, and so does Rust's Result (and all Rust programs).