Dark Corners and Pitfalls of C++
codeproject.com
codeproject.com
I used to learn C++ at the university (~5 years ago) and have read multiple books on that, however, I would have no idea how to start using the language again and how to figure out which patterns are OK and which ones are discouraged and what should I use instead. All "modern C++" books I found are 10+ years old.
There seem to be so many opinions out there on forums, but I don't see a comprehensive, community-approved place (book or website) where I can learn and understand the differences between the many ways of doing the same thing.
Check C++ Core Guidelines https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC... which is a community approved collection of best practices for modern C++.
I jump into C++ every 5 years or so and I haven't found this to be that big of a problem. Reading a few websites got me up to speed really quick.
Having worked mostly in modern managed languages lately, I just look for the C++ solution that is as close to that as I can and it's now very easy to find that. My C++ code looks very much like my C# code.
My own state of mind when working with C++ is that it feels good then do it, if it feels bad then go out and see if there is a better way.
Memory safety is much less of an issue than it used to be. When you use spans over raw arrays (or vectors etc.) - you can have bounds checking; if you use std::array - you won't make invalid accesses. As for memory management - that's essentially a solved problem, you just use smart pointers, and generally not allocate yourself.
It's true that you _can_ shoot yourself in the foot with C++; but it's becoming increasingly more difficult to do so unintentionally.
This is demonstrably false: https://twitter.com/LazyFishBarrel/status/112900096574140416...
Additionally, new "safe" C++ APIs aren't: https://bugs.llvm.org/show_bug.cgi?id=34729
Apple's iOS 12.4 release fixes 37 CVEs, 28 of which are memory unsafety (75.7%).
Microsoft's statistics corroborate this: https://www.zdnet.com/article/microsoft-70-percent-of-all-se...
Importantly, none of these sources show any improvements over time. If we saw meaningful improvements in "modern C++" codebases, I'd be much more willing to accept "modern C++". As it stands, there is no such evidence that "modern C++" provides any demonstrable benefits regarding memory safety.
I don't know of any study comparing memory safety of "modern C++" vs "legacy C++" code. None of the references you provided contains any information about that, so I think it's unfair for you to pretend that it does. From my work over the last years I can tell you that the move from C++98 to C++11 and to a smaller extend the move from C++11 to C++14 has reduced memory corruption bugs by a significant amount. The way we teach C++ to junior engineers is very different to what it was before, a lot of practices that used to lead to unsafe conditions are frowned upon nowadays. It's by no means perfect compared to Rust, but it's definitely not the mess that it's often characterized at when people point to code that would never pass code review nowadays (ie the original article).
std::string_view is a borrow interface, not a safe owner. It is a sharp knife, to be sure, but it's not billed as being safe, either, and it's not broken.
Best practices do not fully eliminate segfaults. This is a practical observation.
True, but modern C++ encourages the use of range based for loops and std::algorithm functions which are safer to use.
Also, if you want to check for bounds use std::vector::at which checks for bounds at runtime.
vector::at does check bounds, is your argument that programmers shouldn't have a choice there or do you just not know the standard library that well?
Last I checked there's still a lot of C++ code in legacy production systems -- and a lot that has never ever used a smart pointer.
I stopped reading at this point.
But, ok, I didn't really. I also noticed the code was quite dated / inopportune:
* Has a "X-er" class - which probably shouldn't exist in the first place.
* Using raw `delete` and `new`
* Using unions instead of std::variant's
* Using C library calls instead of `<algorithm>` functions (memset vs std::fill)
* Using `rand()` instead of the standard library's facilities (even though it's broken https://www.youtube.com/watch?v=LDPMpc-ENqY&t=329s)
So... it's outdated and inelegant C++, regardless of any actual errors.
E.g. C++11's std::move, which may have partially solved the problem of copying objects (which everyone had their own way of avoiding anyway), but introduced its own puzzles about the validity of the objects after the move.
In practice it's really not a puzzle. Most of the time you're using std::move it's because you don't care about the object you moved from, you're about to drop it on the floor.
The other times you either manually reset it or just read the docs of the type to find out what it says the state will be after move. It's not really any different from any other method documentation on a class.
unique_ptr (or other unique_* types) are vastly worth it, which did not otherwise have a way to work prior to C++11's move semantics.
https://stackoverflow.com/questions/3413470/what-is-stdmove-...
The fixes don't create their own problems. They can and are used as intended, usually with little or no problems. What does happen is that they open up new possibilities and program expressiveness, and those have new issues/challenges.
For some types of high-performance infrastructure software like database engines, there really isn't a better alternative unfortunately. While there are other non-GC languages like C and Rust, equivalent software requires significantly more lines of code due reduced expressiveness, sometimes with a performance impact, and there are some types of code safety that are difficult to enforce in those languages.
Because C++ is so expressive in a systems programming context and is rapidly adding valuable features, it makes it difficult for other languages to catch up to the point where there is a direct architectural translation from the latest version of C++ to some other language. I know quite a few shops that actively pull their code bases forward when a new C++ version lands, which is usually pretty painless, to take advantage of new features/safety and added tidiness.
My problem with C++ is that writing "best practices C++" in 2019 is so different from writing "best practices C++" in 2011 that it may as well be a different language. And if it's going to be a different language, I'm going to prefer a language and toolchain that started from a cleaner slate, such as Rust. For example: C++11 got rvalue and move semantics and that was pretty awesome, but it took me a ton of mental overhead to cleanly reason about interfaces where I wanted to support move semantics. I think Rust's borrowing semantics clean up a lot of C++'s memory ownership/management issues into a simpler model that I just don't have to think as hard about.
I sometimes still use C++ because it's easier to work with existing code. For example, if I am prototyping something that requires hitting OS API's, C++ is often the best combination of easily calling into the APIs while still getting modern conveniences.
I wish I could like D but I just don't, some of the decisions it made I think were mistakes.
Go's FFI story is unfortunate, and it seems too narrow in target usage anyway.
C gets a lot of rose-tinted love, but I'd never chose it over modern C++.
And... well that's sort of it, isn't it? What else is there in this area?
Like what? D is everything I ever wanted C++ to be. The only reason I feel compelled to learn Rust is peer pressure, but if it were up to me, D would be the language everyone is getting excited about instead.
I get a general impression of D being in just too weird of a space. It's occupying a zone somewhere between C/C++ & Java/C#, and I'm not sure that's a place I ever want to be. I'd rather just use Java/C# if I need D's feature set, and if I don't I don't want to deal with the headaches that result from trying to avoid them (or use -betterC mode which seems to just be what D should have been from the start)
I mean, I do use the GC, it's just that it's never been something that I care about, just like I don't care how exactly new and delete are implemented in C++ (are they just malloc and free wrappers? Eh, who cares.)
1) Because interop with non-GC (aka, C) code is terrible from a GC'd language. D is better here than other GC'd languages as you can still build smart pointers via RAII, but it still sucks. It's a significant mindset shift in random spots in your code.
2) Custom allocators can be hugely important in achieving certain optimizations or runtime behaviors. This can either be for just performance reasons or in some cases are mandatory for correctness such as if it's a real-time thread.
It's partially why Rust and modern C++ are getting a lot of attention in that space, as they provide a lot of higher-level niceties but don't require the overhead of a GC.
Are you kidding me? What's next, EBCDIC and zOS?