C++ Core Guidelines
github.com
github.com
I actually really liked the way Apple did it for Objective-C. Don't use exceptions unless something is really going sideways, instead use NSError. I'm not saying this is the correct pattern for C++, but I don't personally think exceptions are the correct pattern either.
Do you have any examples in mind where it's unclear to you whether an exception would be appropriate?
Honestly I'm not sure where to draw the line specifically, but I obviously err more on the side of "don't use them". Exceptions in C++ are costly and imo control flow gets all wonky with them...I find it's easier to reason about a program if the error handling is there with the rest of the logic instead of being a list of things that can happen at some point in the above code block.
In typical C code you get to propagate error check statements all the way everywhere or risk bugs. Or use central mechanism like errno and risk thread safety and overwrite while also not knowing about the source of the statement.
In C++ you might have the same problem. Even std::optional (which defers the exception to actual value get) is not best as it loses the information on who made or set it...
Most code would check or call value_or, buggy code would throw. There's still the complication of the unsafe std::optional interface, which can only be solved by wrapping/rewriting IMO.
The STL unfortunately doesn't always follow this approach, and tends to overthrow.
Signaling why something failed without exceptions is still unnecessarily tricky in C++. Not because it can't be done, but because there's no standard guidance on how to do it. There are 3,385 ways to do the job, so in many cases it's better simply not to.
catch (Pig) { // now Grunk need find pig.
The amount of frame unwinding code generated can get really big,
etc.I wish C++ provided means to preallocate memory for handling task (2 buffers for 4k, map for N objects of type T, ..., ok? then run this). Instead, there is a russian roulette powered default allocator, that can out-of-mem at any time...
// big task will create map<int, int> with 1000 elements
MyAllocator a;
a.reserveMemory(????); // <-- what to put here?
bigtask(a);How could it make sense if bigtask(a) is a separate function, maybe in another DLL / shared object ? Maybe bigtask is not even written in C++ but in C, Rust, D, whatever. Maybe it's not even using malloc but directly OS primitives.
if you are ready to take the runtime hit you could use the polymorphic allocator support and pass your allocator object down the chain.
See http://en.cppreference.com/w/cpp/memory/polymorphic_allocato... ; http://en.cppreference.com/w/cpp/container/vector ; http://en.cppreference.com/w/cpp/container/map .
I don't understand why you are making a distinction between table-like and non-table-like containers: both can take allocators.
But in general, I don't really understand why you would want to do this: how do you expect your code to differentiate between the containers that you want to be affected by the memory changes and the ones that you don't want to ? If you don't want to make this difference and have a blanket coverage of, say, every std::vector called by your function it means that you cannot use any external library in your function, since other libraries may have other allocation requirements that your code would break. This looks like this would completely break encapsulation.
The cleanest thing to do is to do like RapidJSON for instance and just pass allocator objects in your functions : http://rapidjson.org/md_doc_tutorial.html#MoveSemantics
Because for map<> in general I cannot know how much allocations and of what size will it need to store 1000 elements. I might glean it for particular implementation of STL, but not via any api.
Doing anything bearssl-like, "No dynamic allocation whatsoever", just does not work with STL.
> you cannot use any external library in your function, since other libraries may have other allocation requirements that your code would break.
Exactly. Most C++ libraries are not made with the idea of allowing you to police resource allocation. It's malloc all the way.
I was saying this from the point of view of the library author, not the code. e.g. for instance I spent some weeks optimizing allocations in a library that I'm working on, and am then making assumptions in the rest of the code according to the optimizations I made. If I did let users of the library change the allocation policy, I would have to introduce costly runtime checks everywhere to ensure that the invariants I set do still hold, that I really have enough memory to do what I must, and abort / throw in case such an invariant is broken. All of these situations are less desirable than enforcing my allocation policy in my code.
Have datasets of typical execution runs, profile test executions and gather statistics for ideal average sizes.
Gut feeling for sizes never ends well.
Go got this right. If there is an error, return it to the caller. The error can be a function, and the caller can call it when they want. This returns programming to a single sequence of events.
You can handle Segmentation Fault, but you need to know what you are doing. In Go it's the same.
Go documentation is very clear on the difference between the two, actually. I've never been confused.
(I think the panic()/recover() mechanism is intentionally crude in order to discourage people from over- or abusing it.)
My POSIX knowledge is a bit out of date, but if I remember correctly what actually happens is not guaranteed to work across all UNIXes.
Besides, signals are OS specific, while C++ exceptions are compiler specific.
what do you put in "all UNIXes" ? Xenix 1.0 ? MULTICS? Because all current unices (https://upload.wikimedia.org/wikipedia/commons/7/77/Unix_his...) would have no problem running recent GCC and C++ exceptions.
I mean, for hell's sake, C++ exceptions date back to 1990. Nowadays you can throw exceptions on damn 16-bit microcontrollers (https://en.wikipedia.org/wiki/TI_MSP430) ; I doubt there's a relevant, non legacy unix where this does not work.
Go with panics moved to regular error returns doesn't seem like it would lose anything.
It's never been that way. What's a page fault?
> No matter how you slice it, exceptions are still a goto under the covers.
"break" is also a goto. What's your point?
> If they are truly exceptional, you might as well call exit() too.
That's completely unacceptable for robust software. Programs can recover from almost all errors and make forward progress, usually via staging some kind of rollback. Tearing down a whole process in response to error is a ridiculous extreme that works only in a few narrow domains.
> Exceptions are a mistake, plain and simple.
Is that why almost every language ends up adopting them in some way? Even languages that start out vehemently anti-exception, like Go and Rust, end up with the full try-and-catch exception toolkit. That's because, despite protestations, exceptions are extremely useful.
Rust (and as far as I know, Go) have pretty much remained the exact same with regards to their implementations here. Go uses panic/recover a bit more liberally than Rust uses panic/catch_panic; using panic for control flow is not idiomatic, and so is not done. Since you can turn them into an abort, it's not something you can rely on when writing a library, and so I'm not aware of any significant libraries that do so. You couldn't even catch panics for a long time, and the possibility was mostly added to prevent UB with regards to extern functions.
In both language environments, the availability of a compiler option that breaks a language feature doesn't erase the existence of that language feature.
What about the built-in memory and concurrency safety?
What about coroutines...?
Exceptions is following the "let it crash" philosophy from erlang, instead of broken recovery code scattered all over your code. (Crash doesn't necessarily mean a linux process in this context, more of a high level) This is a very powerful concept that allows you to think about the bigger picture such as if _anything_ unexpected happens inside this transaction, return a error to the user and continue processing the next request from the next user. Error handling should be done by layers, not by grunt work.
There are situations where I agree you might want to know how a certain function can fail, here I think javas checked exceptions strike a good balance between control vs verbosity as you just have to add a throws-clause if you don't want to handle a specific error at that point in the code.
This is a poor argument; the same could be said for almost all control flow statements: if/else, while, for, break, continue, (early)return ... they're all implemented using "goto under the covers". Moreover, lots of C programs use goto to do their error handling!
Seeing "throw" statements as gotos misses the point: you can't really create spaghetti control flow using "throw" statements. Instead, you can see them as "return on steroids".
It's true that "try-catch" makes it hard to know where your code is gonna "jump" ; but that's the point : as the "thrower", you don't have to know, and you don't /want/ to know. Think about the standard libraries for Java, C#, Python, D ... they don't have this luxury. If when using "throw", you're wondering "where's the catch", then you /probably/ have a design issue.
>This is a poor argument; the same could be said for almost all control flow statements: if/else
It's technically possible to implement if/else (and loop conditions) with unconditional jumps and offsets, but for reasonable instruction sets (that support conditional jumps) they won't be "goto under the covers."
Error codes also generally discard context and encourage a "just abort" mentality, especially with respect to memory exhaustion. That, or they become so general, mechanized, and macro-ized that they might as well be exceptions, but worse in almost every way.
Exceptions are your friends.
At least these days we have class-level initializers to lessen the pain a bit and reduce the number of situations in which you have to write a constructor.
struct foo {
int bar = 5;
};Could you give an example of this specifically? Are you thinking of e.g. a File class whose constructor opens the file? Because every such example I can think of is one where it ultimately seems inappropriate to me to use exceptions to signal the error. Both from a conceptual standpoint (it might be something you have no control over, so it's not a programmer error) and from a performance standpoint. Example for the latter: say the user gives you a list of hundreds of thousands of files that are to be copied somewhere "if they exist". Imagine half of them don't exist. Now your program is going to have to run through hundreds of thousands of exceptions to signal this.
Exceptions are indeed used for handling exceptional situations such as actual errors. If you only want to copy a file it it exists, then check if it exists (race-prone but fast) or create a function that copies a file only if it exists.
Or you know, swallow the nonexistence exception. An optimizing compiler might be able to elide the throw statement if it is inlining the copy function.
The only issue seems to be that few people are able to install or create a good top level error handler in case there is an actual unhandled failure.
std::logic_error?
In most cases where it is a bit gratuitously thrown it can be avoided.
You might want as well to redefine any error as a programmer error.
....what in the world? no... for domain errors there's, erm, std::domain_error. C++ Reference/StackOverflow/MSDN/etc. already explain all these. But it seems I have to copy-paste them here?
"Domain and range errors are both used when dealing with mathematical functions." [1]
"std::logic_error reports errors that are a consequence of faulty logic within the program such as violating logical preconditions or class invariants and may be preventable." [2]
"std::domain_error may be used by the implementation to report domain errors, that is, situations where the inputs are outside of the domain on which an operation is defined." [3]
[1] https://stackoverflow.com/a/641972
Sure, you can give up on copy constructors altogether and manually manage resource copies, but doing so impoverishes the language, wrecks one of its best features, and makes programs both more verbose and probably a bit slower.
If you have to do an OS call to duplicate the underlying resource, assume the class isn't copyable in this way. It's almost certainly possible to make it moveable, and this will get you the bulk of the benefit: you can return it from functions, and you have a std::vector of it.
(Also worth noting that close (2), which presumably you'd call from the destructor, can fail with EIO or EINTR. I don't consider looping on EINTR ever safe, but that's up to you. EIO, on the other hand - what are you going to do about that? And if you don't call close from the destructor, what the hell use is this class anyway?? Overall, I don't think value objects make very good wrappers for a POSIX-style file descriptor.)
It was instructive. That said, there's nothing wrong in general with a class that owns a file descriptor, and making this class copy constructible as a legitimate design decision.
> Also worth noting that close (2), which presumably you'd call from the destructor, can fail with EIO or EINTR.
IO errors from close(2) should be ignored. If you care about durability, call fsync before you close. Even if close "fails" with an IO error, the underlying FD is still closed. EINTR does not actually happen.
See extensive discussion http://austingroupbugs.net/view.php?id=529
Most cases where exceptions are useful are when the alternative is knowingly causing such a state that will inevitably lead to a seg fault. It's much easier to raise at the point of failure instead of trying to limp until the application eventually crashes or loses you all your money.
Think vector::at (good) versus std::stoi (bad).
(What happens: you get an exception at the moment of dereference and potentially lose the information about the cause.)
Given that this is a C++ thread, you’re right, but that’s an issue of the particular language, not the idea generally.
Secondly... yes, that's why exceptions also need good logging/tracing for best results.
But, I like it. Fully and clearly expressed intent for the handling of errors.
Writing supposedly smart code seems to be a weird culture thing in C++.
I've lost count of the number of C++ devs I've worked with that get the "Mmmm, donuts!" reaction to templates, and leave a stinking pile for everyone else to debug. I'm sure I'm included in that group of programmers, but I hope, not as much.
> leave a stinking pile for everyone else to debug
I think you answered your own question. The trick is to leave the debugging to someone twice as clever as you. Job done.
Figuring out a compiler error is significantly easier than having to debug a malfunctioning test.
If c++ wanted a meta language, they really should have created one long ago instead of continuing this template madness.
Presumably the alternatives to a standardized, intrinsically unsafe interface are either a standardized, safe interface, which would require introducing safe alternatives for unsafe elements like std::shared_ptr and raw pointers. Or a more felixible interface standard. For example, making your public functions function templates when necessary.
[1] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus#safercplusp...
[2] https://github.com/duneroadrunner/SaferCPlusPlus#the-problem...
https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/rt2ivJnc4hg%5B1-25%5D
Also, e.g.> P.6: What cannot be checked at compile time should be checkable at run time
Great! I want to do it! later...
> F.4: If a function may have to be evaluated at compile time, declare it constexpr
Ok. You can't put static_assert in constexpr function. You can't put runtime assert in constexpr function. You can't put debug print in constexpr function. How do you even debug that?
Use an IDE.
Explain.
For example on Visual Studio, paths not taken on conditional code get grayed out.
The slightly more serious question: is there anything like this in Vim or Emacs-land? I somehow doubt it... I, for one, have never seen something like it outside of the big IDEs (Visual Studio, IntelliJ, etc.)
Always been an IDE fan since Turbo Pascal 6.0 with its Turbo Vision based IDE.
You provide a set of static assertions for the use cases you aren't sure of. constexpr functions should be thoroughly unit testable at compile time.
If you're worried about production compile times, put the static assertions in your test code.
void f(size_t x) {
assert(x); // calling f with 0 is bug
}
constexpr f(size_t x) {
?????(x != 0); // <- what to write here?
} constexpr int hard_stuff(int x, int y) {
return x + y;
}
void hard_stuff_test() {
static_assert(hard_stuff(2, 3) == 5);
}
?> It injects the hosting machine's filesystem semantics into your program, in addition to locking you down to a vendor. Our recommendation is to write in ISO C++
Erm. What. Compiling anything other than a one-file program "injects the filesystem semantics into your program". I mean... your program is stored in a filesystem. #include uses a filesystem. What utter tosh.
And as for locking you down to a vendor, even niche compilers that you've probably never heard of support it.
https://en.wikipedia.org/wiki/Pragma_once#Portability
I'd take anything from there with a boulder of salt.
So it's not "tosh", it probably just doesn't correspond to your way of writing C++ code.
As of C++17 you can: http://en.cppreference.com/w/cpp/error/assert
You can also throw exceptions in constexpr functions since C++14.
[1] https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...
I guess I have to dedicate and report 1h for learning and self-improvment during next week.
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC...
The only implementation so far seems to be from Microsoft: https://github.com/Microsoft/GSL/blob/master/include/gsl/gsl...
What are some good online resources for learning "modern C++", for someone who already knows several languages?