Written on: 2022-10-16

C++

Here are some quotations from Bjarne Stroustrup, the creator of C++:

If you detect a slightly defensive tone there, you're probably right. C++ is an extremely widely used programming language (and in some industries, by far the dominant language) and has been so for decades now. It also attracts a lot of criticism.

Complexity

The fundamental problem with C++ is that it is a very large and very complicated language. It is so large and complex, with many different features that interact in subtle and surprising ways, as well as a good deal of inconsistency, that it is extremely difficult to read an arbitrary chunk of C++ code and be certain precisely what it does.

Perhaps you think this is only a problem for novices: perhaps you think a real C++ expert wouldn't find this so hard. If so, let's talk about Scott Meyers. In case you haven't heard of him, Meyers is a very famous C++ expert. He is probably the most well known C++ trainer and consultant, and has given many keynote talks at many C++ and software engineering conferences for decades. He is the author of the most popular C++ textbooks (Effective C++ and Effective Modern C++). Whenever there's one of those panel discussions at a conference about programming languages, there's a good chance Meyers would be the C++ representative. Along with a couple of other people, he was the public face of C++ for many years. What I'm trying to get across here is that it is hard to find someone who is more of a "C++ expert" than Scott Meyers. Well, it was hard to find someone...

In 2015, Scott Meyers retired. Only a couple of years later, he stopped accepting errata for his textbooks because he could no longer tell whether or not they were valid. In his own words:

As you may know, I retired from active involvement in C++ at the end of 2015, and in the ensuing two and a half years, I’ve forgotten enough details of the language that I am no longer able to properly evaluate bug reports regarding the technical aspects of my books. C++ is a large, intricate language with features that interact in complex and subtle ways, and I no longer trust myself to keep all the relevant facts in mind. As a result, all I can do is thank you for your bug report, because I no longer plan to update my books to incorporate technical corrections. Lacking the ability to fairly evaluate whether a bug report is valid, I think this is the only responsible course of action.

That's pretty amazing. After only two and a half years, someone who was a world-class expert in C++ for decades could no longer remember how it all worked.

For an example of C++'s complexity, take a look at this short quiz (with solutions) written in 2013: Variable Initialization – or Is It?

The semantics of list initialization were changed in C++14, and again in C++17, and yet again in C++20.

I challenge anyone to read those four articles then claim with a straight face that C++ isn't ridiculously complicated.

Alternatively, how about std::visit?

In 2014, a year before he retired, Scott Meyers gave a talk at a D conference (D is another programming language) that contains a few more examples of C++ fun: The Last Thing D Needs (video).

Subsets

In practice, no-one can hold the whole of C++ in their head at once. People learn a subset of the language, then use that. The trouble is that different people prefer different subsets.

Even if a team can agree on which subset to use, over time the members of a team change. Most large, long-lived C++ projects will have some parts written in a radically different style from other parts, using different subsets of the enormous C++ whole.

If you're working on your own — or on a small, proof-of-concept prototype that will be thrown away once the concept has been proved — then maybe you can stick to a consistent subset of C++. But as soon as your work moves from programming to software engineering, C++'s size and complexity tend to cause problems.

And yet...

I don't know what proportion of professional C++ programmers actually enjoy using it, so I'd hesitate to call it popular, but there's no denying that C++ is widely used. It always ranks near the top of both the TIOBE and Redmonk indices of programming language popularity, and has done since those indices were created.

In an interview with TechRepublic in 2020, Stroustrup said the following:

I see C++'s success as a function of its original design aims – efficient use of hardware, plus powerful abstraction mechanisms – and its careful evolution based on feedback from real-world use.

C++ certainly does make efficient use of hardware. One of the core design principles of C++ is the Zero-overhead principle:

The zero-overhead principle is a C++ design principle that states:
  1. You don't pay for what you don't use.
  2. What you do use is just as efficient as what you could reasonably write by hand.
In general, this means that no feature should be added to C++ that would impose any overhead, whether in time or space, greater than a programmer would introduce without using the feature.

However, in reality, There Are No Zero-cost Abstractions (video).

Even when C++ manages to compile away all the runtime costs of its high-level abstractions (which, to be fair, C++ compilers usually do really well) the human costs are significant: the resulting code is harder to understand. More importantly, the language semantics needed to make such code possible is harder to understand. Even if you never write such complicated code yourself, you must pay the comprehensibility tax so that others can.

What sort of "powerful abstraction mechanisms" are we talking about here? What benefits do they provide that could outweigh the cost of so much complexity? Most obviously, C++ provides language-level support for object-oriented programming. But it also provides template metaprogramming, which makes possible the Standard Template Library (STL) with its independent implementations of data structures and algorithms.

In C, unless they are willing to pay a significant runtime cost, the programmer must provide concrete implementations of data structures and algorithms: for example, you could write a linked list of integers, or a sorting algorithm for floating-point numbers. In contrast, C++ allows the programmer to specify abstract data structures and algorithms: for example, a linked list of any type of value, or a sorting algorithm for any value that supports comparison operations. The C++ compiler can then instantiate concrete implementations automatically as needed.

Further, these are not merely aesthetic differences or conveniences: by providing these facilities within the language itself, C++ can do things that C cannot. For example, Resource Acquisition Is Initialization (RAII) is a technique that allows C++ programmers to automatically manage resources, and so avoid resource leaks, that is not available in standard C.

An excellent example of this trade-off between power and complexity is provided by this talk: Embedded Logging Case Study: From C to Shining C++ (video). Though the final result presented in that talk was impressive, I was frequently reminded of a certain Jurassic Park quotation (video).

At the end of that talk, during the questions from the audience, there's also a great example of what Stroustrup called C++'s "careful evolution based on feedback from real-world use": one of the audience members suggests that a useful non-standard compiler feature should be added to the language. The C++ Standards Committee now has many hundreds of members, divided into various sub-committees, all of which receive petitions for new features to be added to the language. Is this a good thing, because it means that C++ is responsive to the needs of industry? Or is it a bad thing, because it leads to all the bad things usually associated with design by committee? As with everything else in all forms of engineering, it's a trade-off.

Yet another cause of C++'s inconsistency and complexity is its backwards compatibility. The language is nearly 40 years old now and, for the most part, has maintained compatibility with its early versions (although not entirely, which is yet another source of inconsistency). On the whole, businesses highly value backwards compatibility.

As Scott Meyers says, the members of the C++ Standards Committee may honestly say that they value simplicity and comprehensibility, but they clearly value them less than they value powerful abstractions and backwards compatibility. The continued popularity of C++ suggests that many people agree with them... and the continued popularity of C suggests that many other people do not. As always in engineering, different people may reasonably choose different trade-offs in different circumstances.

That said...

Though it is true that all engineering decisions are trade-offs, I sometimes think it is a truism used to disguise bad decisions. Alternatively, to put it more charitably, though a certain trade-off might be a good idea in certain circumstances, those circumstances are rarer than is often implied.

In the talk What is C++? (video), the presenters provide some really clear and interesting comparisons with C. While their intent was to explain the historical baggage that they believe is holding C++ back, what really struck me was that, in every case, I think C came out of the comparison better.

My favourite talk from a C++ conference is Mike Acton's Data-Oriented Design and C++ (video). It's an excellent talk if you're at all interested in designing software that takes performance seriously. Near the beginning of the talk, he defines the subset of C++ that his team uses. He does this by listing all the language features that they avoid: it's very nearly everything that C++ adds on top of C. During the questions at the end, someone from the audience asks him why he doesn't just use C instead of C++. He replies that actually he'd prefer to use C: they only use C++ because it has become "culturally dominant" in his industry.

This makes me wonder, how many users of C++ have consciously made an informed decision about engineering trade-offs? How many use it just because it is "culturally dominant" in their sector of industry?

There is definitely some software out there that makes excellent use of the features of C++. For example, Eigen would not be possible in any language without either C++'s low overhead or its facility for compile-time metaprogramming. There clearly are some situations where C++'s combination of higher-level abstractions with the performance usually found in lower-level languages makes it the perfect tool for the job. But maybe, given all the other costs that it engenders, there aren't really that many situations where the benefits outweigh the costs.

Other options

The cost/benefit analysis has shifted to C++'s detriment in recent years, as a number of competitors have appeared that purport to offer its benefits without its most egregious costs. The most popular by far is Rust. It directly competes with C++ as it too combines high performance with high-level abstractions. The Rust Book says:

High-level ergonomics and low-level control are often at odds in programming language design; Rust challenges that conflict...

Rust is for people who crave speed and stability in a language. By speed, we mean the speed of the programs that you can create with Rust and the speed at which Rust lets you write them... By striving for zero-cost abstractions, higher-level features that compile to lower-level code as fast as code written manually, Rust endeavors to make safe code be fast code as well.

The safety provided by Rust is particularly appealing: Rust guarantees memory safety without sacrificing performance. In that respect, it may be a significant improvement over C++ even without all the other benefits made possible by creating a new language without historical baggage.

However, since it is an entirely new language, it does not offer any sort of backwards compatibility with C++, nor is there an efficient way to integrate Rust into existing C++ projects.

This year, Google announced another new language: Carbon is designed to be an improvement over C++ that provides a way to gradually migrate existing C++ projects. As their documentation says:

C++ remains the dominant programming language for performance-critical software, with massive and growing codebases and investments. However, it is struggling to improve and meet developers' needs, as outlined above, in no small part due to accumulating decades of technical debt...

Existing modern languages already provide an excellent developer experience: Go, Swift, Kotlin, Rust, and many more. Developers that can use one of these existing languages should. Unfortunately, the designs of these languages present significant barriers to adoption and migration from C++...

[Carbon] is designed around interoperability with C++ as well as large-scale adoption and migration for existing C++ codebases and developers.

I'm inclined to agree with them: if you're starting a new project today, and you want the high performance and high-level abstractions of C++, you are almost certainly better off choosing Rust.

There's rarely a good technical reason to choose C++ today: it is far more likely to be appropriate for non-technical reasons, such as the fact that you already have a team of experienced C++ engineers who don't want to switch to Rust, or that you have already invested significant capital into C++ tooling and don't want to waste that investment. Alternatively, the choice may have already been made for you: perhaps you need to maintain or integrate closely with a large, existing C++ codebase. With any luck, it won't be long before Carbon has matured enough to be a better option in those situations, though it's far too early to say right now.

If you want high performance and low-level control but value simplicity and comprehensibility more than powerful abstractions, there is always C.