Dearest C++, Let Me Count The Ways I Love/Hate Thee

My first encounter with C++ was way back in the 1990s, when it was one of the Real Programming Languages™ that I sometimes heard about as I was still splashing about in the kiddie pool with Visual Basic, PHP and JavaScript. The first formally standardized version of C++ is the ISO 1998 standard, but it had been making headways as a ‘better C’ for decades at that point since Bjarne Stroustrup added that increment operator to C in 1979 and released C++ to the public in 1985.

Why did I pick C++ as my primary programming language? Mainly because it was well supported and with free tooling: a free Borland compiler or g++ on the GCC side. Alternatives like VB, Java, and D felt far too niche compared to established languages, while C++ gave you access to the lingua franca of C while adding many modern features like OOP and a more streamlined syntax in addition to the Standard Template Library (STL) with gobs of useful building blocks.

Years later, as a grizzled senior C++ developer, I have come to embrace the notion that being good at a programming language also means having strong opinions on all that is wrong with the language. True to form, while C++ has many good points, there are still major warts and many heavily neglected aspects that get me and other C++ developers riled up.

Why We Fell In Love

Cover of the third edition of The C++ Programming Language by Bjarne Stroustrup.
Cover of the third edition of The C++ Programming Language by Bjarne Stroustrup.

What frightened me about C++ initially was just how big and scary it seemed, with gargantuan IDEs like Microsoft’s Visual Studio, complex build systems, and graphical user interface that seemed to require black magic far beyond my tiny brain’s comprehension. Although using the pure C-based Win32 API does indeed require ritual virgin sacrifices, and Windows developers only talk about MFC when put under extreme duress, the truth is that C++ itself is rather simple, and writing complex applications is easy once you break it down into steps. For me the breakthrough came after buying a copy of Stroustrup’s The C++ Programming Language, specifically the third edition that covered the C++98 standard.

More than just a reference, it laid out clearly for me not only how to write basic C++ programs, but also how to structure my code and projects, as well as the reasonings behind each of these aspects. For many years this book was my go-to resource, as I developed my rudimentary, scripting language-afflicted skills into something more robust.

Probably the best part about C++ is its flexibility. It never limits you to a single programming paradigm, while it gives you the freedom to pick the desire path of your choice. Although an astounding number of poor choices can be made here, with a modicum of care and research you do not have to end up hoisted with your own petard. Straying into the C-compatibility part of C++ is especially fraught with hazards, but that’s why we have the C++ bits so that we don’t have to touch those.

Reflecting With C++11

It would take until 2011 for the first major update to the C++ standard, by which time I had been using C++ mostly for increasingly more elaborate hobby projects. But then I got tossed into a number of commercial C and C++ projects that would put my burgeoning skills to the test. Around this time I found the first major items in C++ that are truly vexing.

Common issues like header-include order and link order, which can lead to circular dependencies, are some of such truly delightful aspects. The former is mostly caused by the rather simplistic way that header files are just slapped straight into the source code by the preprocessor. Like in C, the preprocessor simply looks at your #include "widget/foo.h" and replaces it with the contents of foo.h with absolutely no consideration for side effects and cases of spontaneous combustion.

Along the way, further preprocessor statements further mangle the code in happy-fun ways, which is why the GCC g++ and compatible compilers like Clang have the -E flag to only run the preprocessor so that you can inspect the preprocessed barf that was going to be sent to the compiler prior to it violently exploding. The trauma suffered here is why I heartily agree with Mr. Stroustrup that the preprocessor is basically evil and should only be used for the most basic stuff like includes, very simple constants and selective compilation. Never try to be cute or smart with the preprocessor or whoever inherits your codebase will find you.

If you got your code’s architectural issues and header includes sorted out, you’ll find that C++’s linker is just as dumb as that of C. After being handed the compiled object files and looking at the needed symbols, it’ll waddle into the list of libraries, look at each one in order and happily ignore previously seen symbols if they’re needed later. You’ll suffer for this with tools like ldd and readelf as you try to determine whether you are just dense, the linker is dense or both are having buoyancy issues.

These points alone are pretty traumatic, but you learn to cope with them like you cope with a gaggle of definitely teething babies a few rows behind you on that transatlantic flight. The worst part is probably that neither C++11 nor subsequent standards have addressed either to any noticeable degree, with a shift from C-style compile units to Ada-like modules probably never going to happen.

The ‘modules at home‘ feature introduced with C++20 are effectively just limited C-style headers without the preprocessor baggage, without the dependency analysis and other features that make languages like Ada such a joy to build code with.

Non-Deterministic Initialization

Although C++ and C++11 in particular removes a lot of undefined behavior that C is infamous for, there are still many parts where expected behavior is effectively random or at least platform-specific. One such example is that of static initialization, officially known as the Static initialization order fiasco. Essentially what it means is that you cannot count on a variable declared static to be initialized during general initialization between different compile units.

This also affects the same compile units when you are initializing a static std::map instance with data during initialization, as I learned the hard way during a project when I saw random segmentation faults on start-up related to the static data structure instance. The executive summary here is that you should not assume that anything has been implicitly initialized during application startup, and instead you should do explicit initialization for such static structures.

An example of this can be found in my NymphRPC project, in which I used this same solution to prevent initialization crashes. This involves explicitly creating the static map rather than praying that it gets created in time:

static map<UInt32, NymphMethod*> &methodsIdsStatic = NymphRemoteClient::methodsIds();

With the methodsIds() function:

map<UInt32, NymphMethod*>& NymphRemoteClient::methodsIds() {
    static map<UInt32, NymphMethod*>* methodsIdsStatic = new map<UInt32, NymphMethod*>();
    return *methodsIdsStatic;
}

It are these kind of niggles along with the earlier covered build-time issues that tend to sap a lot of time during development until you learn to recognize them in advance along with fixes.

Fading Love

Don’t get me wrong, I still think that C++ is a good programming language at its core, it is just that it has those rough spots and sharp edges that you wish weren’t there. There is also the lack of improvements to some rather fundamental aspects in the STL, such as the unloved C++ string library. Compared to Ada standard library strings, the C++ STL string API is very barebones, with a lot of string manipulation requiring writing the same tedious code over and over as convenience functions are apparently on nobody’s wish list.

One good thing that C++11 brought to the STL was multi-tasking support, with threads, mutexes and so on finally natively available. It’s just a shame that its condition variables are plagued by spurious wake-ups and a more complicated syntax than necessary. This gets even worse with the Filesystem library that got added in C++17. Although it’s nice to have more than just basic file I/O in C++ by default, it is based on the library in Boost, which uses a coding style, type encapsulation obsession, and abuse of namespaces that you apparently either love or hate.

I personally have found the POCO C++ libraries to be infinitely easier to use, with a relatively easy to follow implementation. I even used the POCO libraries for the NPoco project, which adapts the code to microcontroller use and adds FreeRTOS support.

Finally, there are some core language changes that I fundamentally disagree with, such as the addition of type inference with the auto keyword outside of templates, which is a weakly typed feature. As if it wasn’t bad enough to have the chaos of mixed explicit and implicit type casting, now we fully put our faith into the compiler, pray nobody updates code elsewhere that may cause explosions later on, and remove any type-related cues that could be useful to a developer reading the code.

But at least we got constexpr, which is probably incredibly useful to people who use C++ for academic dissertations rather than actual programming.

Hope For The Future

I’ll probably keep using C++ for the foreseeable future, while grumbling about all of ’em whippersnappers adding useless things that nobody was asking for. Since the general take on adding new features to C++ is that you need to do all the legwork yourself – like getting into the C++ working groups to promote your feature(s) – it’s very likely that few actually needed features will make it into new C++ standards, as those of us who are actually using the language are too busy doing things like writing production code in it, while simultaneously being completely disinterested in working group politics.

Fortunately there is excellent backward compatibility in C++, so those of us in the trenches can keep using the language any way we like along with all the patches we wrote to ease the pains. It’s just sad that there’s now such a split forming between C++ developers and C++ academics.

It’s one of the reasons why I have felt increasingly motivated over the past years to seek out other languages, with Ada being one of my favorites. Unlike C++, it doesn’t have the aforementioned build-time issues, and while its super-strong type system makes getting started with writing the business logic slower, it prevents so many issues later on, along with its universal runtime bounds checking. It’s not often that using a programming language makes me feel something approaching joy.

Giving up on a programming language with which you quite literally grew up is hard, but as in any relationship you have to be honest about any issues, no matter whether it’s you or the programming language. That said, maybe some relationship counseling will patch things up again in the future, with us developers are once again involved in the language’s development.

40 thoughts on “Dearest C++, Let Me Count The Ways I Love/Hate Thee

  1. It is as complex as you write it. You can write spaghetti with any language.

    You don’t need to use all the features. (Spoiler alert: code “fancyness” won’t actually make your software any better)

    1. The problem of throwing a bunch of crap into the language isn’t the main one.

      The misguidedness of much C++ “modernization” over the last 15 or more years is proven by the continued existence of header files.

      I’ve been a professional programmer for decades, with a good portion of it in C++. No way should we be dealing with header files and their associated bullshit in this millennium.

  2. I share your pain and frustration. I very rarely laugh out loud at blog posts, but this one cuts so deep it’s all I could do.

    But, still, I continue to use the language.

  3. I remember getting into the C++ realm in the late 1990’s. At my core, I’m a C coder, so the jump to C++ seemed like a good direction.

    As I dove into the internals of what it was doing, I began to write C code that pretty much did what some of the C++ “capabilities” did, just using struct’s and function pointers. It was an exercise in learning, for better and for worse. This was fun.

    As C++ began to mature I began to move away from it. As my roles took on other technologies.

    For me, C++ can seem like a full size Swiss Army knife, all the blades, screwdrivers, tweasers and such. But sometimes you just need a good old single blade knife. Sure, you don’t need all those other capabilities, but they’re there if you want to use one. In the end, maybe less is more.

    Thanks for the stroll down memory lane…

  4. The main problem with C++ is that there is too much to it. It’s not possible for a normal person to completely wrap their head around it. As a result, you can look at some C++ code and have no idea what’s going on in it. Sometimes C++ code is not possible to follow without an advanced IDE that can show you several levels of definitions. Some people seem to use C++ as a challenge to demonstrate that they can write code using the most obscure features of the language.

      1. Absolutely NOTHING—and, most certainly, not the learning of a language—can raise “…programming into the realm of “engineering”…”.
        But don’t take my word for it; here’s what two of world’s foremost computer science pioneers had to say about this:

        “Software Engineering is Programming when you can’t.”—Edsger Dijkstra

        “When someone builds a bridge, he uses engineers who have been certified as knowing what they are doing. Yet when someone builds you a software program, he has no similar certification, even though your safety may be just as dependent upon that software working as it is upon the bridge supporting your weight.”—David Parnas

    1. My feeling is you don’t wrap your head around it. Keep It Simple Stupid (KISS). There is absolutely no sense in using every ‘feature’/’function’/corner case of the language. More important is keeping it simple to maintain for you and the next person in the years to come — even if it takes more ‘code’.

      I don’t use fancy IDEs. I still use just notepad++ on Windows, or Geany on Linux. And I still use c/c++ . I’ve used C/C++ since the 80s. Probably will until the day I get kicked off the planet. C/C++ does everything I ever wanted to do in a compiled language no matter what the ivory towers keep telling us what we ‘should’ be using (Rust being the latest darling).

      Thanks for the article.

      1. Couldn’t agree more. If a codebase is too complex to keep an overview of it in your head, you should scrap or refactor it. I did use Visual Studio Pro 2010 and other IDEs, but these days I mostly just use Notepad++ and Vim. Auto-indent and syntax highlighting are musts, everything else you can do yourself.

        Always consider the future you a few years from now who has to look at and work with the code you’re writing today.

  5. An interview question i came up with and was proud of was “what’s the worst thing about your favorite programming language?” It was a great show of just how far into the weeds an applicant had gotten. I love seeing long form answers like this.

    I will admit a loathing for C++ in no small part because CS161-CS163, the freshman intro classes at Portland State, were in C++ because that was the preference of the professor that usually taught them. I was top of my class in all three so that loathing isn’t personal, but a result of my work as a head of CS tutoring down the line. To have a hello world with so many things to “learn about later” was not a good intro to programming.

    1. My C++ classes at the university went like this: type code in CodeBlocks during class – it runs, all fine. Save it on USB stick. At home re-write it on paper, scan all the pages and e-mail them to lecturer. Only then you get a “pass”.

      Why? As the man himself told us: “when you write code on paper, you’ll memorize the language”.

      I guess that didn’t work out. Even today I hate C++ with a passion and my go to language is C, bash, Python or assembly.

    2. Pascal was our college ‘learning’ language. I learned C on my own during college, which was good, because the job I took right out of college was all about programming in C for real-time systems. C is so simple to learn… and use. I never understood why people had problem with pointers… but some do. C++ came later with Borland C++.

  6. Despite knowing and using many programming languages professionally and casually, C++ is still my favorite. It has both the flexibility, control, and performance that seems to suit me very well. True, it’s missing many features I wish it had, but many times I can code a solution for a friction point and put it into my library.

    Regarding Ada, I love many features of that language, having written several commercial projects in it. One thing I greatly dislike, however, is that they copied Pascal and COBOL’s verbosity. It’s not only more tedious to type, it tends to turn code into a huge wall of text. I much prefer C++’s middle-of-the road conciseness–not as verbose as Pascal and not as concise as Perl (or APL.)

    1. Personally I find Pascal, VHDL and Ada very easy to read as it’s literally just English text and you’re not deciphering symbols half the time like in C++. That symbol soup is also where I completely bail on languages like Python and Rust, preferring the easy to read verbosity of COBOL and Fortran.

      1. Rust isn’t so bad. I find it highly readable but do admit it takes some time to learn. The one that probably scares brand new people is the ? Operator but it makes error handling so nice, and so concise. The turbo fish is sensible but kind of an odd duck.

  7. I started using C++ in the mid-1980s when the only available implementation was cfront. I liked it at the time as it was a good fit with how I wrote C code. The introduction of the templates hairball was the first warning sign; I then spent about two years taking heap corruptions and memory leaks out of other people’s code, because although C++ had become the “Hot” thing, the people jumping on the bandwagon and using it most definitely weren’t. The Decree absolute came through in the early 90’s and I haven’t touched it since, unlike C which I’ve continued to use. What started off with promise rapidly descended into the abyss as soon as the the C++ standards process fully got hold of it.

    There was a joke in the 90’s that every new feature added to C++ was an attempt to fix the problems introduced by previous new features added to C++, 30+ years on it’s still true, sadly.

    It’s a trainwreck, all the complexity of higher level languages with none of their benefits. I have no idea why it’s still alive. Please die C++, just die.

    1. You saying you have no idea what the language is used for perfectly demonstrate your incompetence. The language is irreplaceable, that’s why it can’t die. And nobody will replace it, because tremendously more effort is required comparing to just continue with it. And thanks to such people as yourself the language is very high in demand, higher than any other languages.

    2. I have looked at new languages like carbon and crack(LLVM). . . Maybe the pupose of modern C++ is to document all of the features of high-level languages. So much flexibility, is it actually useful? To compile many different modules (low and high level languages) seems right.

    1. Be honest, have you ever developed projects in Ada? If not, you were probably like me before I did so: convinced that Ada was cumbersome and clunky. Instead using it made me realise just how cumbersome and clunky C++ is when it comes to actually building something.

  8. You’re doing the same thing again, despite having been pointed out at how wrong you are. auto has nothing to do with “weak typing”. It is not even a type inference, merely a type propagation.

    Answer one simple question: is Haskell “weakly typed”? Agda? ML?

  9. C++ was great when it was C with object orientation (1990). The STL started adding complexity, but it made sense to have a container library as part of the standard rather than writing your own every time, a linked list/map etc. Templates made the STL feasible, but really it could have been done with the existing OO constructs, and this is where I think things started to go badly wrong. Too much crap was added to make the language capable of dealing with every edge case people could think of. Small, relatively obscure solutions were solved by extending the standards and making the language lose its simplicity and cohesiveness. Now its extremely complicated, and has become a language that you will literally find yourself breaking good design at the end of a project just to get the product out the door on time. You can not control a large team of engineers design decisions, so ultimately its the integration that causes issues. C++ is better than C for programming large projects (unless you’re on an embedded platform – then its suicide), but its not a good language to use for applications that sit on modern OS/platforms like Android, iOS, or even Windows – as the platform can abstract a lot and run a virtual machine easily (garbage collection abstracts memory management). I still use C++ for code I want to tightly control and optimize, where performance is a requirement, and I use C for embedded platforms/firmware. For higher abstraction I use Java for Android, and C# for Windows, for ML I use Python. And this is why C++ is only one language of a programmers toolkit, because there is no one size fits all, so you either specialize in one language in your career and hope its last forever and work on similar platforms, or learn multiple languages and have to flip between them, breaking your sanity before retirement.

    1. This.
      Back when C++ was object oriented version of C, which we did object in before C++, it was very powerful and welcome. I started with cfront as well and have the same reaction to templates and STL, really wordy and really confusing and could have been done differently. As far as I can tell it’s just been a race to add in every other languages feature to attract adherence like where some sort of religion or cult. One of the big pain points in C++ was the IO stream library which was pretty useless as a replacement for print. So no one used it which was good because that’s a feature of the language you can still program in plain old C.

  10. well, I’ve been writing c++ pretty much since it came out, and know how to design, write, and test programs written in it. That said, only one language currently is tempting me to walk away from it – and that is Rust.

    Yest, it’s a different paradigm (which I’m coming to after 50 years of programming..), and you have to get everything right for it to compile – but invariable when it has been compiled it tends to work first time, and run fast. Much better than all that other crap out there like python and java etc..

    1. I really enjoy writing Rust. The code is more often then not very easy to reason about, the correctness/safety guarantees are so refreshing, the package manager has a nice user experience, the documentation is same, and idiomatic rust runs real fast. It’s hard not to love it.

  11. Nice article.

    I put C++ down a few years ago. I’ll only touch it for one offs using a few key libraries that don’t exist in other languages. Nowadays for projects I choose I write rust and more rarely use a scripting language. I’m mostly over OO languages. I use them for work but would never chose to do so for my own efforts.

    i would love to try Ada I just don’t have time to learn it, or have a great project for it.

  12. I bought Turbo C++ somewhere in the late ’80-ies or early ’90-ies, and the first few weeks it was a struggle to teach it to myself, but once I got over the syntax quirks I started to like the languages (C and C++ are really two different languages). Before that I had dabbled a bit in basic, but I found it a horrible language (Why did they ever add newlines to the end of print statements, that’s such a horrible feature), but more important, the myriad if incompatible basic dialects for different systems are a big turn off for me. With C and C++ there was already a lot of standardization in Ansi C / C89.

    I also find it quite amazing how well the language scales from small uC’s with a few kB of flash and 1kB of less of RAM all the way to the TOP500.

    One thing in this article I definitely do not agree with is that it’s a “simple language”. With the different standards & extensions over the last 50 years, and a whole lot of stuff that should not be used at all, but has to be maintained for compatibility with old systems, I guess that C++ is probably the most complex computer language ever created. But “normal” programmers only have to know about 10% or less of it.

    Another thing that Maya misses is the mention of love / hate for namespaces in the Boost libraries. In my own (hobby level) programming, I barely use name spaces at all, but for libraries such as Boost, that get used in wildly different environments and have to be fully debugged and (proven?) reliable, “abundant” use of namespaces is simply mandatory. There is (apparently) a huge difference between “application programmers” and the level of knowledge needed to be a good library writer / maintainer for C++.

    And this hits a third string. I can fully understand that “application programmers” do not have any appetite for the “politics” of maintaining / extending the standards, but that does not mean that work is not important. I am very grateful myself for the amount of standardization in C & C++, and disregarding that would be quite disrespectful or ignorant. (No offense intended).

  13. it’s neat how the early choices in language design have a tendency to cascade into every later choice. i know when i have made languages, i have always been bitten by this tendency. that is, my early choices have always looked like mistakes to me. i find myself doing something obviously stupid, and i know, it is inevitable because of a mistake i made early on.

    java is a good example of a language that profits from its early choices. i think because of its vaguely scheme pedigree, the fundamental concepts are all effective simplifications. they do make it hard to achieve performance goals on occasion but they don’t force overcomplication on every subsequent feature.

    C++ is the worst example of a language suffering under its early choices. at every step — and there have been a lot of them by now — the step was evaluated by whether it was a simple hack to implement the new feature. but every time they fell for a mirage. in the first version of Cfront, they had one novel feature, and one simple hack. then they added a second novel feature and a second simple hack, BUT that second hack broke the first hack! the sophistication isn’t O(n) where n is the number of hacks, but rather O(n^2). every hack interacts with every other hack.

    so exceptions are a pretty simple hack — i’ve just recently visited the gcc attribute((cleanup)), and it is indeed very simple. but it interacts with class inheritance and now it’s complicated. virtual base classes seem like a simple hack but now they interact with exceptions! then everything interacts with class layout, and before you know it there’s no part of your compiler that isn’t nested UNTESTED special cases. C++ compilers are ALL buggy af. and slow.

    but the worst thing is the meditation on the inline keyword. very early on in C++, they developed this obsessive thought that any amount of wrappers can be “free” if you meditate on inlining long enough. and now with every new feature, they try to keep this promise. so it’s not just inlining anymore, there’s a zillion fancy template / generic programming features that you can meditate all based around the idea that it’ll be as efficient as C if you get the meditation right.

    the problem is that this has made the STL excruciatingly complicated to implement. it’s not a simple statement of the problem, not even a simple statement of the solution. it’s a ratf— of meditations on partial solutions to the problem, and because nothing is isolated, every meditation interacts with every other meditation. it makes STL very verbose and very impenetrable. and because C++’s isolation is so poor, this mess impacts user code and user debug sessions.

    and then the punchline is that in the end, the code is not as efficient as C. your basic list iterator in C, for (x = head; x; x = x->next) can be expressed in C++ iterators, and with -O, the compiler eventually spits out something that is impressively efficient compared to the STL nightmare that it took as input. but it is still slower than the C code! every time! so they ruined every chance they had for clarity in pursuit of this myth of C-like efficiency, but still didn’t reach it.

    the crazy thing is, the thing that really puts the sting in it…ocaml exists! ocaml is truly generic programming. when you are using it or reading its spec, you cannot even sense the slightest hint that there was a deep meditation on C-equivalent performance. and yet, when you use the code, it’s damn close to C performance. the first thing i implemented in ocaml was actually faster than C, because the algorithm i implemented was such a better fit for ocaml than it was for C (iow, there was a performance bug in my C implementation).

    the C++ dream is alive and well, just C++ is the opposite of it.

Leave a Reply to HaHaCancel reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.