3 ms·
There are still some high-performance software domains where (modern) C++ is unambiguously the best tool for the job. Database engines are a good example of thi
by jandrewrogers 3y ago
There are still some high-performance software domains where (modern) C++ is unambiguously the best tool for the job. Database engines are a good example of this, they pervasively break the compiler's understanding of object lifetimes which requires flexibility in the language that C++ can explicitly express in an ergonomic way.
No one learns all of C++, you just need to learn the bits that are useful for the kinds of applications you write. If, for example, you are building database kernels then you are unlikely to use any of the STL containers (even std::array and std::vector are infrequent) in most of the code. However, you will need to know how to manually fix-up object lifetimes using semi-obscure language features you would never need to know about for most server apps.
If you stay within a relatively narrow application domain, the useful parts of C++ for that domain crystallize pretty readily in my experience, which is much easier to learn.
- jstimpfle 3y agoI would be interested how you "fix-up object lifetimes"?
- jandrewrogers 3y agoSure, it is pretty simple, just esoteric. The compiler attempts to track object lifetimes for the purpose of optimization e.g. if the object contains a notionally immutable value it doesn’t have to re-read the value during the object’s lifetime. A lot of compile-time code optimization is based on knowing when an object at a memory address is changed or destroyed. If the objects don’t get destroyed but merely disappear, or worse, are replaced with a different object at the same address, and the compiler can’t see that at compile-time then the compiler assumes it doesn’t happen. This violates strict aliasing rules in C/C++ even though there are legitimate reasons for this to occur. C/C++ systems code, like Linux, commonly disable strict aliasing rules because it causes undefined behavior, even though it also disables certain optimizations. In the last several revisions of C++, they have added blessed methods of informing the compiler when these object lifetime rug-pull situations occur, so that it can optimize around them without disabling the optimizations enabled by strict aliasing. This includes functions like std::launder, which acts like a constant folding barrier i.e. the compiler can’t assume that constants at that address prior to the barrier call are still operative when generating code. There are a couple other functions in C++23 that explicit take a memory address and explicitly start a new lifetime with an arbitrary type, without constructing a type at that address; these are no-ops, all of these functions are compiler annotations, they don’t generate code. These are blessed ways of doing more hack-ish ways of effecting the same result in recent versions of C++ (e.g. the memmove/launder trick). In old versions of C and C++, the ways of doing this were technically undefined behavior but the compiler writers unofficially provided ways to work around this that would behave as needed since it had valid use cases, which is not a great way to work. This doesn’t just affect lifetimes, though that is most of what the fix-up is about. Mutability of references is similarly fuzzy and it is impossible for a good design to guarantee that multiple mutable references don’t exist, so you need other mechanisms to guarantee they never conflict. However, these fix-ups are above the level of the compiler. In the extreme case of database kernels, most objects have an ambiguous lifetime and uncertain address. Consequently, you have to create object reference wrappers that hide what is required to ensure strictly defined behavior in these cases and to ensure conflicts can’t happen. Wrapping your object references with some small hygienic functions can eliminate the issue.