Embracing Modern C++ Safely, Book Review
cppstories.com
cppstories.com
Looking behind my back there is a shelf of another 30+ books full of C++ (Meyers, Sutter, Alexandrescu, Langr, Feathers, Stroustrup, Lakos etc) that I have read page to page. However, I am certain that any sufficient C++ developer can embarrass me by showing me a corner of this language I have never experienced before. It just never stops. The things I have to keep in mind while coding a solution for the actual problem at hand can be astounding.
But then, I really like coding in it as there are these rare cases when I’m coming up with an elegant and straight to the point solution. I think I have some kind of Stockholm love-hate relationship with this tool of choice.
And when my friend starting working at Google (in C++), I gave him my pile of books.
And, much of it is obsolete, supplanted by straightforward core language features. Their usefulness was demonstrated by use in the wild of Alexandrescu's workarounds.
I hate Alexandrescu's most famous book for its mismatch of title ("Modern C++ Design") and content ("Unwieldy C++ template tricks you should probably avoid").
Definitely Stockholm-Syndrom in my case. Was the first language in which I was forced to work on some larger codebase. Since then I can't take the complexity of any other object-oriented language serious. All of them are basically a subset of C++.
In practice, people writing modern C++ code do not struggle with memory safety, so it has been a good trade.
People avoiding modern coding style, such as Lakos and his minions (and, apparently, Fuschia and Chrome authors) do not get that benefit.
A: "Modern C++ has no problems with memory safety." B: "But what about X? X was written in C++ last year. X has problems with memory safety." A: "X is not modern C++."
That "memory unsafe languages produce vulnerabilities" is an empirical claim. I can give you gobs of data. If you actually think, "In practice, people writing modern C++ code do not struggle with memory safety", then you should produce some data that shows this to be the case.
So, my take is -- okay, prove it. Because, my guess is, your claim is mostly a feeling you have about your code (yes, simply good code vibes) rather than something you can demonstrate to others.
Then why do we have so many memory safety bugs in, for example, modern webbrowsers? I'm relatively sure that the Chrome team is pretty competent, and yet...
string_view really is no different from a naked pointer. Modern C++ treats naked pointers with well-deserved suspicion. I never have any trouble because pointers are always strictly evanescent values.
So you believe then, that the majority of C++ code written at e.g. Microsoft is not 'modern'?
Quote from [1]: "~70% of the vulnerabilities Microsoft assigns a CVE each year continue to be memory safety issues."
[1] https://msrc-blog.microsoft.com/2019/07/16/a-proactive-appro...
Maybe some new code is modern - but there is a tremendous legacy code base that can’t possibly be (and Microsoft has released enough open source that you can verify this yourself). Also retrofitting isn’t magic - the boundary between old and new will always cause problems.
Also Visual C++ team is quite keen in brigging Rust like safety (as much as possible) to their static analysis tooling.
This doesn't mean c or c++ are bad or something. But, yea...
Rust makes it so you can do this yourself for most things. It's not always convenient but it's the best I've seen so far.
I'm interested to see how automatic formal verification of rust code is going. Super interesting area, think a team at AWS is working on it, and a few other groups.
Most modern programming languages have one package manager dedicated to the language, and for good reason imo.
Part of the problem is, the c++ culture is kind of like the c culture, most people would rather write their own packages from scratch then leverage a community. I don't blame the languages, they were around before git was common.
I feel like I am bashing c++... I don't mean too, it's a great language and it does have good packages(some I prefer over what is available in say Rust), but most projects I've seen not using visual c++ do the 1990s thing and don't use these tools. Maybe it's their age, never looked hard at "why", being honest.
If on the other hand we are counting quality packages, all major ones are available.
Please go try rust, not for its memory safety but for all of its other qualities that make it better than c++ in my book.
I would contend the cost of doing things “safely” is much higher in C++. since a human being has to mentally do all the work the compiler would do in a safe language.
Somewhere out there, a startup is writing a browser in pure safe rust, and there won’t be any memory errors in it because they’re never gonna take on any tech debt and it’s never gonna ship.
If you race a skilled Rust team against an equally skilled C++ team to build some big complicated fast software, the Rust team would likely ship first, and with less bugs.
The C++ team will eventually ship too, but it will take much longer. The software will also be of high quality, and very slightly superior performance, but there will be a couple of memory leaks, maybe a couple of exploits, and possibly a tricky segfault, somewhere down the line. Maintaining the C++ team’s software without introducing further issues will require superhuman intelligence, so it won’t happen - there will increasing issues as the team turns over and the detailed understanding of the code is lost over time.
The Rust team will mostly suffer from frustration about how bad async is, go down the rabbit hole of using it, and then rip it out and replace it with hand-rolled state machines and epoll. Down the line at some point, future programmers will decide this is legacy garbage and replace it all with async again.
There will be no segfault or memory exploits, and a similar number of logic bugs to the c++ team.
I say this having worked on large C++ projects and large Rust projects, and with no particular religious love for Rust other than a grateful appreciation for the compiler.
Most of the powerful, unique features are there to help encapsulate semantics in libraries, enabling libraries to adapt automatically to circumstances of use without loss of performance. As a result, libraries in C++ can be more powerful and useful. The more a library is used, the more resources are available to optimize and test it, so libraries can become extremely robust.
Modern C++, when you stick to the new idioms (RAII, range loops, auto lambdas etc) is almost as compact and succinct as Python with type annotations. One of the biggest differences is readability is that the standard library is not "batteries included" so people roll a lot of stuff themselves.
As a former diehard c++ developer, modern c++ was the reason I started learning Rust.
All the good parts of modern c++, without any of the legacy baggage holding it back, and avoiding all of the things that made modern c++ necessary in the first place.
I still program in c++ professionally but only for legacy programs. For anything new I would avoid it in favour of a modern language.
C++ is not by best friend by far, but I'm happy to gain any convenience and safety where I can get it in the language that I use regularly.
For passers-by: Linux has static libs as .a files, indexed ar archives with object files in them, and ELF shared objects as .so shared libraries. In AIX, .a ar archives may hold object files to be static libs, they may hold shared objects to be treated as a shared library (a lot of shared libraries on AIX are .a files rather than .so), and they may hold both 64 and 32 bit files of each to support multiple architectures. A single .a file can be a 32 bit static library, a 64 bit static library, a 32 bit shared library, and a 64 bit shared library all in one.
It has some convenience, in that all the different types of libraries for a package can be found in one place, but I've always found it annoying, especially when you have very specific goals to accomplish and need some libraries linked statically and others dynamically.
It felt a bit ironic using a UNIX that was to certain extent similar to Windows in some workflows.
It has exactly one obviously useful piece of advice: don't make circular dependencies between libraries. In practice, that means organizing in layers.
Most of us don't depend on many libraries where the authors of the libraries know one another, so it is easy to follow, even by accident.
It has lots and lots of obsolete advice, though.
And yet converting code to Rust is hard. It is very tough to convert object-oriented C++ code to Rust. The models are too different.
I honestly think it's the other way around.
It was meant to supplant C, but C users, as a rule, are defined by seeing a thousand other languages go by and not jumping. So Rust mostly picks up discontents from other languages, including Java, Go, and, yes, C++.
The danger in appealing to discontents is that they are likely to jump ship for the next shiny thing.
That seems like a rather unusual suggestion to me. Are there commonly used languages/situations where optimization level changes semantics/allowed constructs? The only thing I can think of off the top of my head is stuff that relies on tail calls be optimized in languages that don't guarantee tail call optimizations, but I think that's a relatively niche case compared to debug vs. release builds.
I also feel that that suggestion could arguably make the language harder to pick up, since something that worked in debug mode might require extensive refactoring for release mode. Given how much Rust relies on the optimizer to see through its abstractions, that seems problematic.
In addition, I suspect (but don't know for sure) that writing code that works correctly in both modes can be tricky - think of something that uses multiple threads, where "release-mode code" can rely on the lack of mutable aliasing but "debug-mode code" cannot. Either you maintain two versions of the code or you write code that doesn't rely on the borrow checker guarantees, which seems like it would defeat the purpose of having the borrow checker in the first place. This is a gut feeling, though, so I wouldn't be surprised if there were a way around this.
Finally, I'm not sure I agree with the notion that this change "[does] not alter the language at all." It's like the concept of "allowing a debug build to complete despite [type] complaints" in C++. You're going to have to change the language somehow to define the semantics of ignoring type errors, and I'm not sure how this will help newcomers or how easy such a change would be to implement.
Out of curiosity, what other measures did you have in mind? Or can you point me somewhere which discusses similar measures?
I don't really see the point of disabling the borrow checker anyway. I assume the intent is that it would make incremental development easier, but as someone who has written probably 100kLOC of Rust I feel like it would take a ton of work to implement (ie: making `miri` radically more powerful and generalizing it) and solve... no real problems for me. I get that a beginner is going to see bigger wins there since they will hit borrow checker errors earlier, but even then, I learned rust in 2015 when the borrow checker was way stricter, and I don't think I fought very much with it - I learned immediately that I could clone my way out of almost any issue.
IMO a far better investment would be into compiler errors giving better hints, or IDE improvements to show lifetimes, etc, and those would all be global wins instead of something rather niche.
I honestly didn't know that capability existed. Just to make sure I'm reading the right thing, is it the technique described in Equality proofs and deferred type errors: A compiler pearl [0]?
I have no training in programming language theory, so I can't give a good evaluation on how applicable this technique is to Rust. The first thing that jumps to mind is that I think it relies on Haskell's laziness to avoid evaluating the thunk (?) representing the type error if it isn't needed, but that's not to say that a different approach can't be found that doesn't require laziness (assuming I read the paper correctly).
That being said, given Rust provides types which defer borrowing checks to runtime (Cell/RefCell [1]), I think it may be possible to implement something similar to -fdefer-type-errors by e.g., wrapping all references in a Cell/RefCell and inserting appropriate function calls. I would guess the main concern there is fragmenting the Rust ecosystem into code that requires a hypothetical -fdefer-borrowck-errors flag and code that works with "standard" Rust, and tying that to optimization level has its own issues.
It's admittedly a bit different than what I had in mind when I first responded to ncmncm's comment (where I interpreted it as "pretend the borrowck errors didn't exist," as opposed to "use runtime checks as appropriate"), but it does make more sense.
Thanks for showing me something new!
[0]: https://www.microsoft.com/en-us/research/uploads/prod/2020/0...
Obviously code that only runs when built "debug" differs from released code, which ... doesn't run. That is the whole point. Before you ship, you will resolve all the borrow nags, and then there will be a release build, and it will run the same.
In the meantime, you probably have more urgent worries to act on first. The only difference is who decides when, you or the compiler. As is, the compiler decides, period.
(I confess it utterly mystifies me how this is such a difficult concept for so many. Inability to comprehend something so simple bodes ill for judgment on more weighty matters.)
That is true, but I don't see how it's relevant. Violations of the borrowing rules in Rust are like type errors in C++ or C. Warnings are simply not part of the picture.
> Obviously code that only runs when built "debug" differs from released code, which ... doesn't run.
I honestly don't know what this wording is intended to convey. The text before the edit made more sense.
But in any case, I don't really see the relevance. I asked about situations where the presence/absence of optimizations changes language semantics. As far as I know, programmers generally expect that the same code will exhibit the same observable behavior regardless of whether it was built in debug or release mode (i.e., the as-if rule, barring UB/IB/etc., of course). What you're suggesting appears to violate that - something that builds in debug mode may not compile at all in release mode, let alone run the same. How is that better for newcomers?
> Before you ship, you will resolve all the borrow nags, and then there will be a release build, and it will run the same.
This feels like it's doing a lot of hand-waving. By way of analogy:
"Before you ship, you resolve all the type nags, and then there will be a release build, and it will run the same"
If you started with a program where everything is a void*, "resolve all the type nags" is potentially a huge amount of work. There's absolutely no guarantee that you will only need to make "minor" changes to get your program working in release mode. Maybe what you wrote is good enough, maybe you need to completely restructure your program. That certainly does not sound very newcomer-friendly to me.
> In the meantime, you probably have more urgent worries to act on first. The only difference is who decides when, you or the compiler. As is, the compiler decides, period.
Couldn't this argument be extended to other aspects of a program that can be statically checked? Why stop at the borrow checker?
(Is it really so hard to understand how a program that exists and can be executed differs from the entire lack of any such program?)
It does suggest that the prospect of Rust ever being made more accessible to new users is slim to none, as, thus, is displacing C enough to move any needle, more's the pity.
We will need to rely on people graduating to use of modern C++, instead.
No, but again, I don't see the relevance here. What I have been trying to say is simple: tying language semantics to the presence or absence of optimizations is going to pose challenges that could easily overshadow any potential advantages a more "relaxed" debug mode might provide for newcomers. Suggesting that the borrow checker be able to be "turned off" is not novel, but the idea of tying it to optimization level is, and that is what I have been trying to discuss, without much success.
Nobody has proposed "turning off" the borrow checker. This is another red herring always thrown up. Nobody has proposed "turning off" the borrow checker. Maybe imagining somebody did is your trouble? But I doubt it.
At some risk of triggering another massive incomprehension storm, you might better compare the suggestion to turning const violations in non-release builds into warnings (while imagining that over-aggressive intolerance of const violations was driving away potential users). Yes, fixing your const violations before release might be a chore, even a big chore, but it should be very, very easy to imagine other actual program logic problems that might be tackled entirely independently of them.
The code with the const violations in might end up deleted, in the process, so that being obliged to fix them first would have been a pure waste of effort.
I mean, if you really want to be pedantic about it, "debug" vs. "release" means precisely nothing since they can be arbitrarily customized. However, in the context of this comment chain (newcomers to Rust), who are likely to be using cargo in its default configuration, "debug" vs. "release" primarily comes down to optimization settings and the generation of debug information, neither of which change program semantics for the most part.
The only exception is overflow checking, which is disabled in release mode. While I'll freely admit this does weaken my argument, I assert it doesn't suffer from the main flaw that yours does, in that debug mode is less lenient with respect to overflow checks, and turning them off for release mode doesn't cause compilation errors. This is significantly more friendly to newcomers, though I still think the semantic difference between debug and release is not ideal.
> Nobody has proposed "turning off" the borrow checker
Nobody in this thread? Sure. Nobody on the Rust dev team? Also sure. I assert (albeit without evidence, unfortunately, though I suspect you know better than I do) that that sentiment is not that uncommon among newcomers, though.
In any case, I think you're reading my comment too literally (though that's partially my fault for being unclear). The intended meaning was that adding some way to "bypass" borrow checker errors is not new, whether by literally "turning off" the borrow checker or through some other mechanism as in your proposal.
> you might better compare the suggestion to turning const violations in non-release builds into warnings (while imagining that over-aggressive intolerance of const violations was driving away potential users).
Yes, this is one possible comparison, but as I stated in previous comments, an analogous comparison applies to every other statically-checkable property, and I would argue that they all face the same issue: that what you write in "debug" mode may be unusable in "release" mode, which does not seem like a good experience for a newcomer. In addition, there's the question of why the argument should stop at the borrow checker - why not also have a mode which allows a program to compile despite the compiler complaining about type errors (which is a superset of const violations)?
So in the end, yes, I can imagine ignoring one set of problems allowing a programmer to focus on another set of problems. I can also imagine solving one set of problems by ignoring another resulting in an entirely useless solution because the ignored problems are actually/incidentally important, in which case the ability to compile code in an "intermediate" state is cold comfort. Maybe preventing said intermediate code could have clued in the programmer that the solution they were headed towards was unfeasible, as well. Hard to say how this tangle of hypotheticals turns out.
But all this is moot. It is clear that the overwhelming sentiment is that not even the tiniest change to make Rust easier for new people to adopt should find sensible consideration. Thus, Rust will see too little adoption to forestall fizzling, with the full approval of its most ardent fans. I will be sad to see that happen.
I'm not sure this has to be true. While I'm not sure how a compiler might deal with something like std::vector<int> v = some_unordered_map with the existing language semantics, something like working with technically-incompatible pointers might be malleable enough to work with. In such a case, the compiler knows how to generate otherwise-appropriate code, but according to the language rules it can't. Ignoring the type error in this case could mean just generating the code anyways and letting the cards fall as they may, much like what may be done for const or borrow violations.
> It is clear that the overwhelming sentiment is that not even the tiniest change to make Rust easier for new people to adopt should find sensible consideration.
Are there other language which make similarly-situated concessions to newcomers?
Rust has the biggest concession to newcomers I have ever seen offered, it will not let you compile code that contains many commonly encountered show stopping confusing as sin errors...
The person claiming this, is in the same breath claiming that a book written by experts is not worth reading. Assuming ncmncm is an expert at c++: How confusing does a language have to become for that to even happen?
Rust is the language that, if not adopted fast enough, will pass its sell-by date and fizzle, like so many languages before it. Its true fans should be pulling out all the stops to try to make it easier to adopt. Instead, most do their utter best to prevent wider adoption, containing it as much as possible to the ragged few like themselves willing to tolerate any infelicity.
Rust's fate will be chosen by fans' actions, not their beliefs. Those actions are its doom. Judging by those, Rust will end up yet another potentially interesting language that never took off. I will know whose fault that was.
Rust is boring and technical. Boring in the "it just works and when it doesn't it's not hard to figure out why" way. You didn't say this, but for others passing by, it would be a mistake to view rust as a "shiny thing". People use it for a reason, barring syntax entirely.
Take a massive codebase like Libreoffice. How would you even start to migrate this to Rust?
It would still be a lot of work, but you might see benefits at intermediate releases this way.
I've also never seen anyone say that Rust is closer to C than C++, that also seems weird.
Person 1 says, “Zig is a viable C replacement, Rust is a C++ replacement.” Person 2 responds, “Rust is absolutely a C replacement, Rust is so much more simple than C++, that they should not be compared. That simplicity, which is much closer to C than C++, is why Rust should be viewed as the ultimate C replacement.”
So, it’s not a clear cut statement that Rust is closer to C than C++. However, I certainly have taken that as the argument in most discussions I have seen.
Rust needs to compete with ISO C++ current, not ISO C++98.
In any case, C++20 has Concepts, which where used clear up the template error problem.
That said rust is pretty different from c++. Most people don't compare rust to c because c is so heinously unsafe and rust being the polar opposite. Ironically, rust was indoctrinated as the second supported language to the Linux kernel, alongside C. So they have that in common...
I wouldn't make too many assumptions about the rust community, they are pretty nice smart reasonable people who tend to write c/c++ for work...
Look at the first comment here: https://news.ycombinator.com/item?id=18037566
And there's a pattern matching proposal: https://news.ycombinator.com/item?id=21933590
And CJJ23 only just added `string::contains`, `string::starts_with` and so on after Rust had them. Coincidence? I think not! (j/k obvs.)
for clown in clowns
This is pretty different from how C++ iterators work:
for (auto clown = clowns.begin(); clown != clowns.end(); ++clown)
So, to "try to compete" we'd expect Rust to adopt the C++ features here right? But instead, in C++ 20 now you can write:
for (auto clown : clowns)
Notice that, as so often, to get here C++ has to add layers of hacks. In Rust clowns is simply any type that implements IntoIterator. That's all, this feature is just syntax sugar and you can implement it yourself calling std::iter::IntoIterator::into_iter(clowns) and then looping over the resulting iterator -- but C++ has not only explicit handling for the range feature here, it also needs two extra special cases to handle the broken C++ built-in array type and the C++ initialiser structures so that these both do what you expect.
So, five years later, and with extra hacks, C++ gives you the same feature as Rust, and we are to believe that C++ isn't the one trying to compete?
Range based for loops were introduced in C++11 (technically C++11 was published in 2011, but almost all compilers supported this way back in 2008-2009) and it's also nothing more than syntactic sugar. In order to provide support for a type T you need the following functions, which can be free functions or member functions:
begin(T&)
end(T&)
++(decltype(begin(T&)))
The only hack I can think of related to your post is that if T is an array, it uses the free functions std::begin and std::end which are overloaded to work with C arrays.
Little of what "tialaramex" writes is correct, and none of substance.
This is not exactly true. For example, with the introduction of smart pointers, manual memory management is kicked out of C++'s happy path. This makes code far simpler to reason about and safer. Sure, you can still use new and delete, but since C++11 you better have a very good reason to use those.
Nevertheless, it doesn't make sense to complain about not deleting stuff from C++ when a) until recently C++ was repeatedly criticized for having an extremely spartan standard library, b) since C++98 basic features were added to C++ such as support for parallel and concurrent programming, c) there is no feature that gathers anything resembling a consensus on beig worth taking out of the standard.
I'd add that pseudo-standard libraries for C++, such as Boost and POCO, have been popping up and growing in both scope and adoption. This reflects a clear need felt by the C++ community to increase the scope of it's standard components.
Irrelevant. C++'s smart pointers deal with ownership and life cycle management. You can still pass raw pointers to objects managed by a smart pointers.
The only exception is when you're dealing with a framework which already handles life cycle management, such as Qt. Nevertheless, even Qt offers its own smart pointers suite.
Not if the API is taking ownership and you want your program to be correct. This happens far more often than dealing with something like Qt.
Not really. The very, very, very important difference is that the pointer can be null, signalling "no object". For some purposes, this is a critically important difference; in other cases, you should be passing a reference.
> Learn to embrace the pain.
Judging by how much I suffered just to try to implement a sqrt function generically I think that is not an exclusive C++ thing.
> And yet converting code to Rust is hard.
Exactly, that is why many many projects will remain in C++ for a long long time.
Some projects will be coded in other languages, and stay in those languages. Wherever that language fizzles, the project will too.
Anybody who believes this ought to think it an excellent reason to stay away from C++. For actual software projects I mean. Obviously if you want a career writing books (like the one reviewed) or on the conference circuit, or training, or several other prospects, this is great news. C++ is a good language it which to charge somebody $1000 per day plus expenses to tell them what they already knew.
But if you actually write software, or worse, if you need software but that isn't really what you actually wanted to do just a necessity like farmers need to know how to drive a tractor, then this ought to put you off. C++ doesn't want to be your best choice, according to ncmncm anyway, it wants to be a trap you fall into and can't get out of. Run away!
So, the advice above is just to avoid all languages (which, by the evidence, he does, seeming to know nothing meaningful about any).
There is an exception, though. Projects in C have frequently transitioned to C++, often with great success, notably Gcc and Gdb.
For example, the current c++ project I work on (for a large company) has over 1.5 million lines of c++, spanning over 5,000 files, with business knowledge and bug fixes spanning over 30 years.
It's not going to get rewritten in anything else, and the same goes for large amounts of other business software.
The c++ will be staying c++.
I don’t say this out of any affinity for c++(check my other comments for details), rather because for business reasons, the core of this system will always be c++.
In fact we a currently embarking on a multi-million dollar project to upgrade the application to run in the cloud and one of our biggest challenges is finding qualified c++ candidates.
Despite that, due to the complexity and scope of the application and years of business knowledge captured in obscure parts of the code, there is no consideration for it to be rewritten in anything else.
Classical Win32 C APIs are for all practical purposes frozen since Windows XP.
And the answer you practical won't like, when not using COM directly, the advised way from Microsoft is to make use of .NET bindings for the OS APIs, either via C++/CLI, or the new kid in town, C++/WinRT as Windows Runtime Component.
Even if you don't use Windows as desktop, the other desktop contenders, also use COM like APIs on their driver stack, IO Kit, DriverKit, Treble, and XPC/Binder aren't much different from DCE RPC development experience.
The book covers only up to C++14 because that is the latest Standard that Bloomberg has managed to field. They got to use C++11 only a couple of years back. Code written at Bloomberg is not, as a rule, good code. The BDE library much (but not all) Bloomberg code relies on is, while nominally open source, not used outside Bloomberg, for reasons. (When BDE group were directed to start reviewing outside pull requests, all discussion was of how to find time to reject whatever might come up. They need not have worried.)
This is not a good book unless what you need is a doorstop.
Our analysis of C++11/14 features aims to be as objective as possible. Could you please provide any example of what you consider "superstition"?
Keep in mind that the book was heavily reviewed by multiple members of the ISO committee and experienced developers such as Andrei Alexandrescu, Sean Parent, Nina Ranns, Alisdair Meredith, and more. There's zero chance that "superstition" would have made it true their review, and I'm eager to see what you come up with :)
Your post is also spreading falsehood. Bloomberg has been using C++17 for several years already, and many teams nowadays use C++20 and even C++23 features. We target C++14 because we find it intellectually dishonest to claim to know the best use cases and pitfalls for features unless we have had multiple years of experience with them in the real world.
Your opinion seems quite biased, I take it that possibly you had some bad experiences at Bloomberg, which can happen as in any other large company.
Still, I'd be happy to hear your feedback and improve the book if you provide some concrete examples of "superstition".
Anything starting from that premise was bound to lead nowhere good.
The 1300-page size will anyway help limit its influence, just as has the gross inflation of Lakos's other output.
It is also obvious you hold a grudge against Lakos and/or Bloomberg for whatever reason.
Guess your crusade of spamming "please don't buy this book" without providing any concrete arguments is the way you present your grudge to the world.
Well, if you ever change your mind and intend on providing constructive feedback, feel free to let me know. I'm also happy to have a call and discuss what could be improved in the book for future editions.
Cheers.
Nobody needs a grudge against Lakos, he generates his own opposition. (You know it is true.)
More recently I've learned rust. I haven't missed c++ at all. Even the more modern stuff I've learned later on...
That said, I downloaded their sample pdf and the nice thing is the code they present is very well commented and so it's easy to read. They seem to take a 'read the code to learn the subject' approach, which I appreciate. Note that it takes a slightly conservative approach:
> "Only the passage of time can distill the programming community’s practical experience with each feature and how well it fared, which is why this book discusses features added up to C++14, even though C++20 is already out."
Maybe I'm missing something but they don't seem to offer the book as a searchable PDF? There seems to be a Kindle edition and paperback only. Worried about piracy or something? Otherwise it might be a useful reference, I guess.
The InformIT page linked from the review has (I'm in the US):
* book for $64
* ebook as ePub, Mobi, and PDF for $51
* book + ebook bundle for $86
The sample PDF is searchable, so I presume the full PDF is as well.
If you're looking at the Amazon listing, then no, I don't expect Amazon to offer PDFs instead of, or even in addition to, a Kindle edition. Oddly, the Kindle edition is more expensive than Amazon's paper copy, or InformIT's ebook.
Based on the sample, this looks like a nice book. It seems to have Meyers-like advice in a more concise, reference-style presentation.
It's a slight concern that all four authors are from the same department of the same company. I don't know anything about Bloomberg Development Environment, but I would be skeptical of a similar offering from, say, Google.
Includes EPUB, MOBI, and PDF
Could you please explain why you believe that this book is not needed by anyone? Especially when it was reviewed and endorsed by ISO C++ committee members and experts such as Andrei Alexandrescu and Sean Parent. Concrete examples would be appreciated.
This limits the sharp edges and in particular makes it easier to identify where they might exist. Perhaps the most common rule is the prohibition of exceptional control flow. This can seem really limiting at first, but encoding the possibility of errors using the type system and forcing callers to handle errors explicitly is really powerful.
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
One of the book's authors actually proposed work to get C++ to a place where C++ 20 could begin actually abolishing the things everybody agrees you shouldn't do, Vittorio's "Epochs" proposal. https://vittorioromeo.info/index/blog/fixing_cpp_with_epochs...
But the preference for C++ 20 was to add yet further stuff. I believe that, unwittingly, the C++ 20 process sealed the fate of the language in choosing to land features like Concepts but not attempt Epochs, I'm not sure it was actually possible, but if it wasn't possible in 2020 it's not going to be any easier in 2026 and it's only going to be more necessary.
The active subset is always the set of features not supplanted by the latest Standard supported by tooling in use. Anything newer is safer and nicer than old crap, so there is no temptation to stick to old crap, except among people who have elected not to learn anything new. Those are typically approaching retirement anyway.
This is particularly true of large code-bases in established firms.
Personally, I think it would be an interesting idea to soft ban a lot of pointer use and only allow shared and unique. As pointed out in an earlier comment, it is a 'python' like experience without the performance compromise.
This said, the exception is when you need to use well tested existing libraries such as libmicrohttp to implement a local REST server without buying into some mega framework.
The part where Vittorio proposed Epochs to fix this?
The part where the Committee decided not to take it?
Do you think that fixing this will magically get easier if you wait longer?
Yes, you will need the source code to C++ software to actually compile it but the relationship to licensing is at most coincidental.
Much of the time when you really can't negotiate source code, it's because it's a "pig in a poke" IP sale, what you're buying was actually either worthless or the seller never really owned it anyway. The video games industry is certainly rife with this.
Also, remember, that ISO document is known for two decades to not actually describe the language as implemented, but the fix (pointer provenance) is controversial enough that the committee keeps "forgetting" to fix it. The ISO document describes an imaginary language which would be incredibly slow and useless if it really existed, the document exists because the real C++ language is sort-of similar to this imaginary language and for some people (though it seems gradually fewer over time) that's enough. Any pretence that ISO requires it to "work for everyone" is nonsense since as documented it isn't in fact working for anyone.
† One of the fun side effects is that you can't explain what ARM Morello ports do in terms of the ISO standard. CHERI reifies provenance, which would be fine if the standards document explained what provenance is, but the C and C++ documents deliberately do not do that. On paper Morello ports just waste space to make some valid programs crash, which is weird.
Ironically you point out CHERI, so when is the Rust memory model going to be fixed to fully support it, and catch up with C and C++ on that front?
Is Aria done with her blog posts?
The ISO clay tablets do happen to work for more people than Rust in its current state, even if they are a bit fragile.
But it is ok, assuming that Rust's main goals isn't to cover all industry use cases supported by C and C++ in 2022.
Rust actually talks about provenance (messing with pointers is of course unsafe so it doesn't need to worry about this in most of The Book) so this isn't a mystery in Rust. Indeed the API as it stood before Aria's work already makes it clear that you're stepping off the edge of the cliff if you try to do address arithmetic and similar provenance defeating magic. Aria's "Tower of weakenings" is about firming up some of those first steps, allowing us to do some Road Runner don't-look-down bullshit and know whether we can get away with it on real hardware with the real compiler, while still forbidding things that are definitely crazy (the ISO documents say such things work in C++, but they do not work in actual C++ compilers, they have, drum roll... Undefined Behaviour).
Aria did a bit more than write some blog posts, for example:
https://doc.rust-lang.org/core/primitive.pointer.html#method...
Again though, this isn't about licenses. Regardless of why you don't have source code, that's where the problem is. "Gee, it's difficult to compile this program without source code" is maybe something where C wants to draw a line in the sand because of its very simple ABI but C++ is a long way past the point where that's useful and people need to stop pretending otherwise, it's a cause of enormous frustration in the community.
Huh?
Also epochs can't introduce backwards incompatible semantics that break across epochs, imagine a crate exporting something whose representation at runtime changes between epochs and is used as parameter in some callback implementated in that epoch.
Rust provides tools which will do the equivalent transformation to your actual code, but of course this transliteration is ugly, so the preferred form for further development of a 2015 edition crate is the 2015 edition code, the transform is useful if you've decided to actually update to a newer edition so that it serves as a working (but perhaps slightly ugly) start.
This is already rather more compatibility than C++ has delivered. Rust 2015 Edition works just fine on a brand new Rust compiler in 2022, whereas a C++ 20 compiler can't always compile valid C++ 98, C++ 08, C++ 11, C++ 14 or C++ 17 code except via a "version" switch of some sort. They are, ultimately six distinct versions of the language.
It's true that this limits what is possible with a Rust Edition. However, because so much is possible it inspires people putting in that extra little bit of effort to get it done compatibly.
Example, Rust 1.0 you couldn't just iterate over an array. The built-in arrays don't implement IntoIterator because that needs const generics and Rust 1.0 didn't have const generics. Fast forward to Rust 2021 edition, and this works. But wait, that's syntactically impossible. You can't have arrays only implement IntoIterator on newer editions, that's not syntax.
So there's a hack. On modern compilers for Rust 2015 and 2018 editions, when you write x.into_iter() if "x" is an array, it silently ignores the fact it knows arrays are IntoIterator and continues considering other options, crucially (&x).into_iter() the iterator over a reference.
That's all the hack does though, so arrays are IntoIterator, you can use them as you'd expect for a container even from Rust 2015 code now, you can for loop over them, you can pass them to functions which want IntoIterator, you just can't use this method call syntax which would always have done something else.
This feature costs Rust a little hack in the 2015 and 2018 edition parsers forever and that's all.
Unfortunely most likely I won't be around to collect it.
For the purposes of defining their "success" people like Stroustrup seem to consider that the firmware developer who compiles their caveman 1990s C code with a Gnu C++ compiler the board vendor provided is a "C++ programmer" although if you ask that developer about say, operator overloading (a C++ 98 feature) they will look blank 'cos even C89 is a bit novel for them.
At the other extreme, HN resident C++ apologist ncmncm seems clear that nothing short of C++ 20 (a language which is documented but not yet fully working in compilers you can get) really counts as C++ and so if you're not writing Modules with Concepts (and presumably reporting Internal Compiler Errors left and right as you work) that's not really C++
Those are some very different size "markets". I suspect Rust is already similar numbers to the latter, but is a very long way off the former.
But yes, a 47 year old Rust will be crusty, however with any luck PL research didn't stand still for forty years and we'll be recommending people adopt something a bit less crusty for new work. One of the things that's different is obviously the Rust community and its ecosystem and that means it's not necessarily about winning converts for "the cause". A better language than Rust is a good thing not a bad thing.
Having a lot of tools in your toolbox doesn't necessitate using all of them in inappropriate contexts. I don't try to hammer a screw just because I own a hammer.
The other thing with C++ is the complexity. The toolbox is so jam packed full of millions of tools I have basically can't comprehend the full ecosystem and how everything works. Additionally I pick one tool and that single tool itself has like 20 different ways of being used with a bunch of edge case gotchas.
Because it has absolutely nothing to do with footguns.
The main factors in picking subsets, beyond personal tastes on coding styles and office politics, boil down to a) introducing exceptions in legacy code, and thus the need to revalidate what is otherwise solid code, b) not be forced to deal with the complexity of template metaprogramming when the value it adds back is negligible.
There have been debates here about C/C++ being "dangerous". And it can be, if you don't know how to use it. I recall discussions where doing in-line assembly in C had a similar discussion.
It doesn't take being an "expert". It does take "understanding what you are doing" but that same thing applies regardless of the language/tool/etc.
It seems significant that, given that the individuals working on these projects are professionals, experts by some definition, they too are encountering issues related to the sharp edges of these languages. Issues that have wide ranging implications.
In light of that, doesn’t saying that “understanding what you’re doing” is all it takes to productively and safely use a language like C/C++ seem simplistic? Even the experts are getting it wrong.
I took a couple C++ classes in college. I’ve worked on both desktop and backend C++ systems in both C++98 and ‘11. It can be a difficult language, it has more than its share of footguns, it definitely helped me appreciate other languages that don’t have similar issues.
[0] https://msrc-blog.microsoft.com/2019/07/16/a-proactive-appro...
[1] https://www.chromium.org/Home/chromium-security/memory-safet...
The problem is the nature of programming makes it so that once a language embeds itself into the infrastructure it's hard to remove or upgrade. Very similar to how most of the front end world is stuck with javascript.
JavaScript as ugly as it is, is nowhere near the monstrosity that C++ has become though.
Would you mind explaining why you believe that the audience for this book is limited to a specific company? Concrete examples would be appreciated.
"Unsafe" has a very specific meaning in our book -- it doesn't refer to security or to the fact to a feature is inherently bad.
It means that the cost/reward ratio of teaching a certain feature and using it in a large codebase to which multiple engineers contribute to tends to be negative.
There's nothing inherently bad about 'final', but it's easy to overuse it for no real benefit, and it can cause reusability issues in larger companies where inter-team refactorings cannot be done automatically. That's pretty much it. There are also some excellent use cases for 'final' described in the book, but they are very niche.
Hence the "unsafe" categorization, i.e. I wouldn't teach this feature to a new hire :)
1300 pages, and nothing useful to say in them.
I don't know if you have been fired from Bloomberg or failed to pass an interview there, but there seems to be vitriol and anger behind your posts.
I think that people would take your "criticism" more seriously if you backed it up with facts and examples instead of repeatedly begging readers to not buy the book :)
But virtual functions have strictly limited usefulness. If your go-to organizational feature is class hierarchies, you are probably not programming well.
Also, 'virtual' functions are extremely useful to define interfaces that separate teams can implement and consume independently.
Variants and algebraic data types are not the answer to anything, and it is not "outdated" to use 'virtual' where appropriate, despite what your other comments might imply.
Virtual functions are a mechanism that has legitimate uses but makes a poor organizational basis for a program.
The book agrees with that statement. If only you had taken 5 minutes to read a few pages before starting to beg strangers on the internet to not buy something that could improve their career just because one of the authors hurt your feelings sometime in the past...
> Virtual functions are a mechanism that has legitimate uses but makes a poor organizational basis for a program.
This is what I would consider "filler", much like the rest of your posts. Opinions presented as facts, without any sort of source, example, nuance, or elaboration.
One of the good things of the language being so all and pervasive is that there is a lot of very good learning materials.
If you don't want to take my word for it, check out the acknowledgements in the book itself -- it has been thoroughly reviewed endorsed by many top-notch ISO committee members and C++ experts.
> I’m still reading this book, and I haven’t read all the pages (1300+ pages!). Yet, I’ve seen all of chapters, read half of them and picked those related to my recent work, tasks, or blog posts. I’m impressed with the level of detail and the quality the authors put into this material. The book has become my primary “reference” for those C++11/14 parts.
With further detail below that.
The author of the review also clearly states that the book was read and provides an unique opinion and perspective over what the book offers.
It is also completely false that the review only comments on the length of the chapters.
Have we read the same review? Or did you stop before "My Views" and the pros/cons? :)
I haven't seen any job postings needing C++ lately but I am sure some niche jobs exist.
Feels like instead of learning the complexities of modern c++ people might just be better off using Rust or any of the other safer programming languages for new projects.
All Adobe products heavily use C++, Most Digital Audio Workstations heavily use C++, All 3D authoring software heavily use C++, Most new core Blender code is now C++ instead of C. Of Course, most game engines are C++ based as well. So this is just the consumer space.
There are domains where there is simply no alternative to C++ in terms of features and speed. Rust maybe, but I don't know enough about Rust to formulate an opinion. No need to say that C++ is absolutely here to stay.