Orthodox C++
gist.github.com
gist.github.com
I would also like to know who or what the "Orthodox C++ committee" is. A search only gets you this exact gist. Is the author himself the committee? That really doesn't put this piece into a better light.
> Exception handling is the only C++ language feature which requires significant support from a complex runtime system, and it's the only C++ feature that has a runtime cost even if you don't use it
I don't think that's true. I believe that if you enable RTTI you pay for it even if you don't use it, which is why it's banned in the LLVM codebase. [2]
> Don't use C++ runtime wrapper for C runtime includes (<cstdio>, <cmath>, etc.), use C runtime instead (<stdio.h>, <math.h>, etc.)
I believe this is still supported, but is officially deprecated, for what that's worth. [3]
[0] http://www.qnx.com/developers/docs/qnxcar2/index.jsp?topic=%...
[1] https://stackoverflow.com/a/9695020/
[2] https://llvm.org/docs/CodingStandards.html#do-not-use-rtti-o...
[3] https://stackoverflow.com/a/13643019/
edit Correction: It's called Clean C not Clean C++
It doesn't hurt that the STL is extremely optimized, and often provides you tools that accomplish what you cared about without lots of the gotchas and pitfalls you're almost certainly doomed to fall for if you're not aware of their existence. Almost every single C project I've seen (including some of mine, I've to admit) end up involuntarily reimplementing some basic algorithm or container, and most often than not it ends up being less efficient, or unnecessarily using the heap.
One thing I think most people largely misunderstood is exceptions; they're actually totally fine if used in _exceptional_ circumstances, as if they were some kind of softer assertion, when you expect some kind of contract to be always true, but calling abort is just too unreasonable. This is basically the same approach Rust and Go use with panic(), and in both languages this facility by default causes the runtime to unwind the stack, as with C++ exceptions.
I think lots of people have been turned off by certain bad designs in older C++ revisions, where exceptions where indiscriminately used for error handing. They were a nightmare to use right, and the lack of helpers like unique_ptr made exception safety very hard.
Modern practices, an arguably better language after C++11 and guidelines/libraries such as the GSL vastly help in mitigating most of these issues, as long as people actually stick to them (i.e., no C style code without RAII).
I may also argue that the whole "C++ exceptions are expensive" argument is 100% moot in 2020, I've been running code on microcontrollers with half a megabyte of ram with them turned on and they add an almost insignificant amount of overhead; they only cause a small amount of binary bloat, but that's nothing compared to the size of certain modern libraries (and storage is cheap).
> and it's the only C++ feature that has a runtime cost even if you don't use it – sometimes as additional hidden code at every object construction, destruction, and try block entry/exit, and always by limiting what the compiler's optimizer can do, often quite significantly.
C++ doesn't do "hidden code" - "if you don't use a feature you don't pay for it" is one of the bedrock principles of the language.
https://en.cppreference.com/w/cpp/language/Zero-overhead_pri...
test() with exceptions enabled is two instructions longer in the normal code path.
Simple analogy: let's say you have a program that has 10 functions in it. You write a benchmark that exercises one of these and never (no, not once, never) calls the other functions. The other 9 are truly dead code. Would you expect the benchmark result to be different between that and a program that only included the one function being benchmarked?
Another giant issue with C++'s "if you don't use a feature you don't pay for it" and interactions of various features with each other is that features you do use tend to be horribly expensive. It seems to me that authors of C++ tend to use some antiquated model of what "runtime cost" means which totally ignores effects of cache locality and code size.
With exceptions turned on there’s a hidden call to _Unwind_Resume. Additionally, the destructor has to be duplicated.
So the only "fast" option is to just not handle errors at all. Which is a terrible recommendation, of course.
There are ideas/proposals how to fix that: https://www.youtube.com/watch?v=ARYP83yNAWk
The concept isn't terrible necessarily, but the execution and condescension sure is. And some of the rules are just pointlessly argumentative. Such as refusing to use <cstdio> and instead using <stdio.h> - those are officially documented to be different things with different behaviors. Blanket banning one of them doesn't improve simplicity, especially if you're blanket banning the "wrong" set of headers. Unless the goal is to also ban namespaces, but that's not called out as such (and namespaces are one of the least contentions C++ features - everyone seems to like them well enough).
Or similarly:
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
So don't use std::vector? Or std::unordered_map? Or std::string? These are all perfectly fine classes, banning them makes no sense at all. Maybe the goal was to ban "hidden cost" classes like std::function, but rolling your own 'C-style' is a hell of a lot more complex & error prone (raise your hand if you've seen a C pointer callback that forgot to have a void* context or a mismanagement of said void* context...)
Also, while iostream is a bit of a mess, having it be strongly typed means that it's, in my opinion, well worth using over cstdio.
One thing I was surprised not to see mentioned here is multiple inheritance. In my experience it gets really messy really fast.
Also I really recommend reading the linked archived Boost discussion about a geometry library, it's quite funny. It starts with:
double distance(mypoint const& a, mypoint const& b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
And after a couple of pages of refinements ends up with: template <typename G1, typename G2>
double distance(G1 const& g1, G2 const& g2)
{
typedef typename strategy_distance
<
typename coordinate_system<G1>::type,
typename coordinate_system<G2>::type,
typename point_type<G1>::type,
typename point_type<G2>::type,
dimension<G1>::value
>::type strategy;
return dispatch::distance
<
typename tag<G1>::type,
typename tag<G2>::type,
G1, G2, strategy
>::apply(g1, g2, strategy());
}
But hey, it can compute distances in non-cartesian hyperspaces so that's pretty cool.There's nuance to banning that since implementating multiple interfaces is technically multiple inheritance in C++. So I think I'd agree with you but with an exception for pure-virtual classes.
> Also, while iostream is a bit of a mess, having it be strongly typed means that it's, in my opinion, well worth using over cstdio.
Orthodox C++ would ban this but you can have both printf-style with type safety with https://github.com/fmtlib/fmt
Which inspired C++20's std::format: https://en.cppreference.com/w/cpp/utility/format
Which is also banned by Orthodox C++
Orthodox C++ bans exceptions, so if an allocation failure occurs within one of those classes, the only thing your program can do is abort.
That’s fine for some programs - Google’s C++ style guide takes this approach, as does LLVM. But for other programs, where aborting on allocation failure is not acceptable, there’s no way to use those classes without using exceptions.
Note the style guide says they’d rather use exceptions but had too much legacy code (and now must have a couple of orders of magnitude of it by now): “On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. … Because most existing C++ code at Google is not prepared to deal with exceptions [don’t use them]”
Google’s non use of exceptions is often used as justification for not using them either, by people who haven’t actually read Google’s style guide.
Also: few of us have Google sized problems.
The comparison you need to make is instead exceptions vs. return values. Unless you're going to argue all errors should abort by default or similar.
I do wish noexcept had better support, or was even the default (C++ and wrong defaults, a tale as old as time). But it's disingenuous to compare exceptions against nothingness.
If you call a function within the condition of an if statement, the compiler needs to conservatively assume that the execution flow from there is either to the rest of the conditionsl or to either one of the catch blocks or the functions' stack unwinding. If there cannot be an exception, the execution flow has none of these additional branches that bog down the DFA.
Sure but that seems like the edge case not the general case? Nearly any desktop or mobile app or game won't have any such constraints, for example. And you've also got overcommit to deal with on those platforms, so it's not like your allocation failure will actually happen at malloc time either.
So outside of embedded, which tends to have a variety of constraints, what even pretends to handle allocation failures across the entire program?
I'm not entirely sure why you'd want a custom allocator for shared_ptr, though.
The deeper rationale is that some systems like to assign fixed memory pools to subsystems. These may be reset or destroyed at certain points in the applications life cycle and the general expectation is that this frees all memory used by said subsystem. If you still want to use the STL in that context, you need to provide custom allocators for everything that potentially allocates.
At least you can write a replacement for shared_ptr. The same is not true for std::function. This is the only named type a capturing lambda converts to according to the language standard and it's an STL type with complex behind the scenes behavior.
A capturing lambda is just a class with an operator(). It's complicated to do what std::function does, but fully possible.
In fact, custom std::function replacements have better lambda support than std::function itself. Such as unique_function in https://github.com/Naios/function2 which can handle non-copyable lambdas.
Don't embedded platforms mostly use something other than STL anyway?
In general, when discussing which language features to use, I would keep in mind that something can work well for an application that is fairly straightforward and has modest performance requirements, while being a complete no-go for a different use case. I'm not saying we should throw C++ in the bin, but I also think many people have their reasons for saying some C++ features are more trouble than they're worth.
[0]: https://stackoverflow.com/questions/42588264/why-is-stdunord...
(Edited to remove the bit about std::vector, as this argument does not apply to it.)
I feel that banning them altogether may lead people to implement stuff from scratch even when they don't need it, creating another possible source of bugs and vulnerabilities.
BTW, vector is contiguous, not continuous.
So optimal unordered map implementations, like Google's absl::flat_hash_map or Facebook's F14 would be similarly banned as they internally allocate.
I'd definitely recommend those libraries over std::unordered_map for sure, but it's not like std::unordered_map is unusably slow or broken, either. It's fairly comparably to Java's HashMap that everyone uses without thinking about it.
that's when you use one of the two hundred alternative implementations which keeps the same API but offer different performance compromises & tradeoffs :
https://martin.ankerl.com/2019/04/01/hashmap-benchmarks-02-0...
If you prototyped with std::unordered_map you'll likely just have to change a couple types and add the relevant includes here and there, rerun your benchmarks, and tada.
> Don't use anything from STL that allocates memory, unless you don't care about memory management.
is not meant condescending but literally. Ergo, if you don't care about memory management (which is fine), then feel free to use lots of STL stuff.
Case in point: for lots of embedded software, memory matters a lot and it's important that it's obvious to the reader of the code what memory gets allocated where. The STL classes you quote make this harder to see. But if you have lots of RAM anyway (ie "you don't care about memory management") then this does not matter much.
A key goal of this list appears to be, I quote, "Projects written in Orthodox C++ subset will be more acceptable by other C++ projects". This particular guideline makes it more likely for your code to be deemed acceptable by embedded programmers so it seems to fit. I don't think it was intended to be condescending in any way. I think the same holds for the other points.
It could've definitely been written a bit clearer though.
I don't see it that way. Plenty of people care about memory management and also see the STL as a fine tool to use. Little is gained from a memory management perspective by avoiding e.g. std::vector. Whether condescension is intended or not, this definitely comes across as talking down to me, and worse, it is incorrect.
If they didn't intend condescension, they should not have put a comic displaying exactly that (and with essentially no further relevance) into the middle of the article.
This isn't really true. All of these containers take custom allocators, which can be tailored to your use case (eg: a stack based allocator).
The important part is "if you don't care about memory management".
In language benchmarks, C++ is often portrayed as less efficient/slower than C. C++ is also commonly shunned when it comes to embedded software, especially on low performance chips. It doesn't have to be! C++ is (almost) a superset of C, and generally, they use the same compiler backend, so your C code should compile on a C++ compiler and generate the same binary.
Now that you know you can write C++ running as efficiently as C, you can start to carefully add features that will keep the spirit and efficiently of C while making your life easier, or even improve performance. For example you may want to replace macros with templates, use proper objects instead of doing like stdio does with FILE*, use namespaces, etc...
That's exactly what Orthodox C++ is about. It is C++ for those who want to write C. There is absolutely nothing wrong with STL containers and smart pointers and all the fancy stuff that make up modern C++, there is also nothing wrong with using languages that have heavy runtimes and garbage collectors, it is just not the use case Orthodox C++ is addressing.
I think that's the good thing about the mess that is C++11 and beyond. You can pick what you want. You can stay low level and know exactly the memory layout of your program. Or you can choose not to have a single raw pointer and let it manage the memory for you.
Note: There are still a good reasons to use C over C++. A big one is that linkage is a lot simpler and more compatible in C. C++ compilers do name mangling to support things like namespaces and polymorphism and require the linker to understand their particular conventions, and you may need the right libstdc++ for your target. You also need to be aware of static initialization.
Source? Even in the somewhat silly computer language benchmark game C++ is fairly consistently ranked faster than C: https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
> Don't do this: http://archive.md/2014.04.28-125041/http://www.boost.org/doc...
Maybe he is looking to use golang?
When some projects/domains even now prefer C to C++, aren't they trading off safety for vague expectation of greater execution speed?
There are a lot of decent ways to use templates; that link is definitely not one of them.
What Boost project churns out is absolutely terrifying in its lack of rudimentary elegance and good programming taste.
In that case, the impetus was to dress up having failed to implement templates, exceptions, and other "new" language features, typically as a result of budget cuts.
In this case, it appears to be, rather, to avoid learning anything new.
Make no mistake: code written to this, or any old-school subset, is Bad Code. Features are not added to C++ on a whim. They are added because they make programming in new C++ a better experience than old C++. New C++ code is faster, safer, smaller, less bug-prone, and more fun than old C++. C++20 is much more fun than C++17, which is better than C++14, which filled out lots of features from C++11.
Do not trust anyone suggesting any virtue in more C-like C++ code, or in actual C code. We left those behind for reasons.
C++23 will be better than C++20.
C++20 has std::format which is insanely more powerful, safe and performant than printf. To say that you should only be using the non-allocating functions is also bad advice. The whole thing reeks of someone who didn't bother to measure where he was actually spending time.
I didn't read the whole thing but I'm expecting that hes bashing exceptions too, even though they don't cost anything other than code size unless you actually throw. And it's perfectly fine to turn them off, if you're fine with abort.
Really, the only thing I can think of that I really dislike about both C and C++ is that backtraces should have been a first class citizens since the early 2000s. I think that's a huge reason why people pick up other languages more quickly too.
https://www.youtube.com/watch?v=hQVTIJBZook
Strip away an older language's bad features. You end up with a sub-language with the advantage of portability and better maintainability.
The problem can be deciding (and agreeing on) which language features are good and which are bad.
Go can be understood as an improved C that keeps much of C's simplicity but adds small, powerful features like interfaces and channels and garbage collection
Go fixes C's well-understood flaws (declaration resembling use, unintuitive operator precedence, unrestricted address math, silent casting, zero-terminated strings, etc.)
Go puts essential C idioms directly into the language (pointer/length is formalized as slices, packages are part of the language instead of just being naming convention, etc.)
The longer I used C++, the more I despised it. I used C++ for 11 years and I literally hate the language. But C has always remained a pleasure, and Go is a continuation/modernization/enhancement of that
Go is mature, stable, widely-used, well-supported, well-understood, etc., so it's fully mainstream
If you like C, you will love Go
Go goes further and puts the blame on its users, who are “not capable of understanding a brilliant language […] the language that we give them has to be easy for them to understand and easy to adopt”[0].
This stands in a stark contrast to one of C++'s design principles, namely that the language shouldn't “force people to use a specific programming style”[1]. C++ not being opinionated is not aesthetically pleasing, and at times confusing. But that wide acceptance to new ideas is exactly what allowed those ideas to graduate from niche languages to the mainstream.
--
[0] Rob Pike at Lang.NEXT 2014. 20:52 @ https://channel9.msdn.com/Events/Lang-NEXT/Lang-NEXT-2014/Fr...
[1] Bjarne Stroustrup, pp.3 @ https://stroustrup.com/hopl-almost-final.pdf
Other people prefer profound language complexity; no comment
Some applications do. Other applications, for which the problem is irrelevant, can now ignore it rather than having the complexity of a solution baked into their foundations.
My experience is that C++ is complex, but it gives you power. I think whether or not you like the complexity of C++ depends very much upon whether or not you _need_ the power that comes with it. If you do not need the power, I can understand your argument that a simpler language is preferable.
If so, it’s a significant impediment to introducing Go into a team that’s currently using C++ - any Go code would be isolated from the rest of the codebase.
My condolences
Being practical, I'm not interested in using an experimental language that could be dead/unused/unsupported in 5 years