Tuesday February 9, 2021
Ah! C++! The most horrible language in the universe. Responsible for everything that is wrong in software engineering since the dawn of man. There are not enough masks in the world to protect you from C++!
Ranting about a programming language might get you clicks, but it doesn’t really move the needle, does it?
If you’d like a more detached conversation about C++ and programming in general, please read on. If it’s late and you just have trouble falling asleep, well, read on too.
Before I get into what my views are, I thought it would be useful to share about how I got there. Also, it’s my blog post, so unfortunately for you, I kind of write about whatever I want.
My first contact with a home computer was in 1984 when my dad bought the first Mac. I was seven years old. In 1987, Hypercard was released, and you could, with Hypertalk, write little programs. I learned a bit of the language by looking at the examples provided and wrote custom animations.
It didn’t take much convincing from the computer to make me fall in love with writing programs.
Shortly after, I got my computer, an Amstrad CPC 6128. I learned Basic and Z-80 assembly by reading books and articles in magazines. Remember, at the time the Internet was only a military and governmental network, access to information was much more difficult.
When I got my first PC, I think it was in 1990, I was 13 years old, and I learned Pascal (Turbo Pascal!) and x86 assembly. I was always attracted by low-level programming because I wanted to understand how software worked at the atomic level.
I do not remember exactly when I started programming in C, but I remember I began C++ somewhere around 1997. At that time, I didn’t have access to a proper STL implementation and grew to (wrongly) dislike the STL because of this, to the point I wrote my own “standard library.”
I got my first job in 1999 and was paid to design Windows application in MFC. At that time, C++ was a sane default for the application I was working on. Those applications would probably be web apps today.
Regardless, I was more interested in kernel programming and eventually moved to jobs that allowed me to do that. I also learned a wealth of other languages such as Ruby, Python, PHP, but remained mostly focused on C++.
In 2006, I got a new job and met a guy named Christophe Henry. He was doing cool hacks with Boost Lambdas, and had a lot of fun crashing Visual Studio 2005. He started working on Boost.MSM during his free time.
It made me realize that it doesn’t matter how long you spend time doing something; if you don’t seek out contradiction and meet with talented people who do things differently, you will never become great.
Christophe was a gateway to the larger C++ community, and that helped me progress tremendously. A couple of years later, I would even co-author a book with Joel Falcou about template metaprogramming. That last bit always make me smile because the first time I was introduced to template metaprogramming I told myself “this is evil, which is why I need to learn more about it.”
In C++, I have written and designed, in a team or by myself, and more often than not entirely from scratch: memory managers, file systems, encryption device drivers, trading systems, and even a database management system from which grew an entire company.
Yet, I do not think I have an excellent understanding of the C++ programming language or its history. I believe my computer science knowledge is superficial at best: I know enough to get things done. If I were to rate my C++ level, I would say I am a solid 2 out of 10.
C++ is like Russian; even at 80 years old, you will still learn new ways to be derogative to someone by creatively comparing them to human genitalia.
It’s not because C++ is a horrible language; it’s because it’s designed to solve the most intricate problems in software engineering (more on this later). That’s ok, though; not everyone needs to be an expert at C++ (or any programming language for that matter); what matters is that you can do your job well and have the right process to learn new things.
After reading this, you can understand why I have a positive bias toward C++. Despite its quirks, it served me well over the years, and I think it’s one of the best languages out there.
My idea is to share with you my current thoughts on C++ and programming, as is.
While this post will focus on C++, I hope you will find that most of the insights are not specific to a language. C++ happens to be the language I know the most.
I like its expressivity and the total control it gives you, which is excellent for the kind of things I am used to working on. The infamous “zero-cost abstractions”.
If my job were designing web applications, I would probably not think C++ is a great language because all its weirdness and contraptions would make no sense to me.
Programming languages are just an abstraction that we use to be more productive. The degree of abstraction you need and its functions are intensely dependent on the problem you need to solve. In the end, it will be all the same: 0 and 1 going through the processor.
Some criteria are different for each programmer: personality, tastes, and cognitive abilities. Of course, to be a great programmer in any language, you need to be smart. The good news is that there are different forms of intelligence, and the sooner you can find out what kind of smart you are, the better your choices.
That is why most languages debates are as pointless as “which martial art is best”: the two parties are assuming they have similar minds and must solve the same problems. The trick is to make sure that the tool you chose doesn’t become part of your identity. In other words: don’t make a religion out of your technical choices.
My personal experience is that where there are great software engineers, there will be great software. The language is secondary. Picasso will draw a masterpiece you won’t be able to mimic with a pencil, and a piece of paper ripped from a notebook.
Of course, there are objective reasons to pick a language. For example, performance requirements, environment support, team capabilities, or the library you want to use are only supported in a limited set of languages. To achieve the vision he has in his mind, Picasso will seek out the appropriate material, set, paints.
Regardless, I strongly encourage you to embrace your own bias. You cannot be entirely coherent and rational about every decision you make. You, human, are inconsistent and irrational.
You did not pick Rust because it is better than C++. You chose it because it is a new shiny, exciting thing. Yes, PHP is a horror, but it gets the job done, and over the years, it grew on you. You write your web apps in Ruby on Rails because you think David Hansson has a cool car, not because it has “superior metaprogramming capabilities.”
You know what? Own it.
You are making a step in the right direction the day you abandon the idea that there’s a perfect solution for the problem you’re working on, a perfect “way.”
As soon as your problem ceases to be trivial, software engineering becomes the art of making the right compromises. That goes beyond choosing a data structure and algorithm and extends to programming styles and even, you guessed it, languages.
Looking for the perfect solution is a way to procrastinate. Often, we procrastinate because we feel we’re not going to do a good job.
Instead, we find creative ways not to solve the problem. Or we bikeshed to feel we are doing something.
It helps when you are struggling to recognize that you are solving a difficult problem. Try to find which kind of complicated problem you are working on, because, chances are, this is a well-studied area. For example, many issues that look “simple” on the surface are very hard. One example I use often is the traveling salesman problem. Indeed, a computer should be able to solve it, shouldn’t it?
Each language has its philosophy. Perl has the “There Is More Than One Way To Do It,” meaning that it’s intentional to be able to do precisely the same thing differently.
Python is almost the complete opposite.
Is one of them better? You probably have an opinion. I don’t. I prefer Python, but I don’t think it’s “better.” When we stop to think in terms of “better,” when we detach ourselves from the discussion, it’s easier to see how we often argue about things that are quite similar (back to don’t make a religion out of your tools).
Of course, objectively, some tools are better than others. Writing programs using perforated cards was a nightmare. I am grateful for all the progress we have achieved in just a couple of decades, and you probably are too.
There’s another element to consider, which is that your world is limited by the language you use. More restrictive languages may tend to box you in certain ways of doing things. And that’s not always a good thing.
Every time someone says, “I rewrote $famous_app in $new_language, and so it’s going to be better,” my eyes roll so quickly that space-time bends around me. It subscribes to the fallacy that changing the horse fixes the problems on the saddle.
John Carmack is famous for writing a quick and dirty program that solves the problem in the most straightforward way and then rewriting this program completely. I love this approach and have used it successfully several times.
On the other hand, we often say, “I’ll just rewrite it,” because writing code is an order of magnitude easier than reading code.
Unfortunately, I can’t come up with a guideline of when rewriting makes sense, and when it doesn’t, this is something you learn with time and experience. I try to answer one question: “is the program complicated because the problem is complicated or because the programmer is incompetent?” The proper way to answer this question is to assume that the problem is much more complicated than you think it is, and unless proven otherwise, the original author made the right calls.
Memory management is one of the trickiest things about programming. The language you use may obscure this to you by having no explicit memory allocation or total control over the allocation primitives of the operating system.
A tired saying is that garbage-collected programming languages are easier to use and less error-prone. That is a superficial understanding of memory management.
The size and lifetime of the program are what makes memory management difficult.
Small, short-lived programs do not have memory management problems. For example, you can elect not to release any memory and this works fine for short-lived programs.
The memory strategy of your language will tell you what kind of problems you will face. Garbage collected languages have pauses and cyclic dependencies to solve. Reference counted strategies can have performance overhead and cyclic dependencies. Manual memory management increases the complexity of the development.
If your program is extensive, memory management will be a challenge that you must overcome. It would help if you learned the memory management logics available in the language you use and efficiently use it.
Scheme’s grammar fits in two pages. The official C++ 17 standard is 1605 pages long. Both languages are hard to master.
A language’s complexity cannot be reduced to the complexity of its grammar or its libraries.
If you do basic cooking at home, you probably have one knife, a pan, a spatula, and some other essential accessories. With them, you can already cook an extensive range of beautiful dishes. One could conclude, “to cook, you don’t need anything more than a knife, a spatula, and a pan.”
One day, you want to bake a cake. Then, of course, you need a whisk, a baking bowl, and many other accessories.
As you continue your journey through cooking, you discover that some rare but necessary tasks require specific tools to be done well.
Sure, you could do without, but have you tried opening oysters with a regular knife? If yes, don’t you miss having five fingers on each hand?
Many languages were designed to be “more secure than C++.” Have you heard about the Dvorak keyboard layout? Dvorak was intended to be more efficient than Qwerty. In theory, it is. In practice, it’s not, and learning a new layout is a pain, so it never gained any traction (and never will).
If you think your program will be “more secure” because you use the language X instead of Y, you will be taught excruciating lessons.
I am pretty sure that, for example, Rust can catch some errors that a default C++ compiler cannot detect, but the gains are going to be minimal compared to what threat modeling, testing, and auditing will bring to the table.
That doesn’t mean that you shouldn’t use Rust; it just means you need to have realistic expectations regarding correctness and safety in programming.
For example, pure C can be a landmine. But with proper tooling, rigorous development techniques, and processes, you can write extremely secure C programs. It may not be cool, and it may be tedious, but it will deliver results.
Correctness is the same. What is difficult is transcribing a solution into a program. We write incorrect programs because the solution is inaccurate, not just because the job of transcribing is difficult. We often don’t understand the problem altogether or the solution’s behavior as we write the program.
That’s is why waterfall development techniques are so inefficient: they assume you can spec out everything in advance, and if you follow the spec, everything will be fine. In my experience, this is false.
Building software is arduous and expensive. We have tools to help assess the correctness of the solution. Sometimes programming languages have features to help us catch inconsistencies but make no mistake; program correctness is very expensive.
The question you must ask yourself is, how much are my users willing to spend for correctness? You never have unlimited time and money.
Most of your program’s performance will come from choosing the right data structures and the right algorithms. Modern C++ has all the tools you need to overdeliver in this area, but so do many other languages.
C++ is associated with performance because of the degree of control you have over what happens. It gives you the super-precise tweezer to build a highly efficient and precise clockwork and make sure you have squeezed every little bit part of it. It comes with robust tooling and an environment to do that. It’s the benchmark against which all other languages compare themselves.
That doesn’t mean you have to use C++ to achieve very high performance; it means that with C++, you can be confident that you can.
However, and that’s where it hurts, your program’s performance will be limited by something else: you, the programmer. Your understanding of all the moving pieces, the compiler, the memory management, the operating system, the hardware… Your capability to ingest all the parameters of the problem and pick a good solution.
That comes into play much more than the language.
C++ has a bad reputation that comes mostly, I think, from two things
Newsflash: you’re not supposed to use everything at once!
Take Excel, for example; the amount of features it has is just gargantuan. It’s a complete operating system. For example, I use Excel very often to calculate how much money I lost on the crypto markets. To do that task, I use 1% of it. That doesn’t mean the remaining 99% are useless, I don’t need them. And I don’t bother about it.
Complaining about something being “too vast” always puzzles me. Whatever amount of knowledge you will accumulate in your lifetime, it will be a drop in the ocean. Isn’t seeking completeness an empty pursuit?
When using C++ (or any large language), the mistake is to learn everything about it. Don’t. And, in C++, you don’t pay for what you don’t use, so there’s no downside.
The other trap is to mix in different paradigms. In C++, it’s a widespread mistake and can make your code very byzantine. Try to adhere to a dialect and stick to it as much as possible. If you try to mix styles within a single code base, the pain will occur. Remember Ghostbusters? Don’t cross the streams.
It is often argued that C++ could be better if it did without the C compatibility. But the C compatibility was required to bootstrap C++, and the seamless integration is extremely convenient in-system programming.
Many subscribe to a fallacy to think that by designing a language from the ground up, they will necessarily end up with a better language.
Like any software project, you do not have to bother with existing users when you get started, and it’s easy to break things. At QuasarDB, we don’t have the same velocity we had two years ago because backward compatibility is a question when we want to introduce a significant change.
C++ is the same, and any language that starts from scratch will eventually have the same problems. Which isn’t a way to say that we should stop inventing new languages, just again, all about setting expectations.
Shouldn’t you stick to C instead of C++? I personally think you can be much more productive in C++ than in C, but that requires more knowledge and discipline because the language is larger. Whether you are ok with that tradeoff, is up to you.
Let us have a look at the intellectual process that made me chose C++ for QuasarDB.
Quasardb started as me scratching an itch in 2009. My Visual Studio was just an empty C++ project. The itch was, we need something that’s not a relational database, that can handle data without any intrinsic limit: no business plan, no clear idea, just an intuition.
The first reason why I chose C++ was familiarity. I knew the language and the environment well enough to twist it in any direction I would need to. Familiarity should, I think, be the number one reason to pick a tool. I am extremely productive in C++, much more than any other language and the STL logic is a fantastic fit for database problems.
The second reason was the availability of libraries. Whatever I would need, I knew there would be a well written C or C++ library for it. That was also important because although I was starting from scratch, I wasn’t planning to write absolutely every little piece from scratch.
The third reason is performance. Wait. Am I not contradicting myself here? Well, yes and no. The program wouldn’t be performant because it’s written in C++; it would be performant because C++ can deliver the highest level of performance when you know how to use it.
And the last one was long term. I had a hunch this could turn into a long-term project. C++ is used to write operating systems and compilers. But at that time, in 2009, C++ was deemed old and soon to be obsolete.
C++ 11 was on the way (it was called C++ 0x because everyone was young and crazy). The first version of Quasardb was using a beta version of GCC with experimental support for lambdas because YOLO.
What I saw made me very confident that C++ would make a huge comeback. I knew the C++ infrastructure would be there for at least the next 20 years.
Would I make the same choice today?
No hesitation. I screamed several times at my screen because of complex memory management issues or stupid mistakes that slipped through the cracks. Still, I know that we would have different, probably worse, problems with other languages.
Recent updates in C++ 17 and 20 make that choice even easier, and forthcoming features such as ranges and concepts are something we are very much looking forward at QuasarDB.
My advice, not limited to languages, would be: ignore fads. To the mind that is still, the whole universe surrenders.
There are many shooting stars in computer science, and investing in a language is a huge commitment. Because it’s not just learning the grammar, it’s understanding the libraries, ecosystem, idiosyncrasies. That takes time.
As of January 2021, the Tiobe index lists the five most programming languages as such:
If you don’t know where to start, learning any of these languages is a good, safe investment. If you venture down the top 20, you are still probably making a good choice. Yes, the list has some limitations, but it’s a good indicator of a language’s popularity.
Outside of that? Your call. While skills are transferable from language to language, you may come to the realization it’s more efficient to learn the mainstream things first.
To be clear: you will never waste time learning a language or a way of doing things, but if you venture outside of the most popular and used languages, you may experience frustration.
If you come from the Java world, you could write C++ as you would write Java. The experience will probably be frustrating (some weird quirks around virtual will rub you the wrong way), but it would work. However, no, you haven’t learned “C++.” You’ve just learned to transliterate Java into C++.
That’s why by “learning a language,” I don’t mean, play with it for a while to see what it feels like (that, you will do a lot). Still, I mean gaining a non-superficial understanding of the language philosophy and being able to solve problems without having to look at stack overflow every few lines.
So should you learn C++? If you’re interested in high-performance programming and like the STL philosophy, I think you will find the time invested extremely rewarding.