Welcome to this round gents and gals, it’s time for one of the big boys. C++ is probably best described as the grand-daddy of many modern, object oriented programming languages. Basically, it has been wielding influence all over the place and it is undeniably one of the most popular languages in existence that clearly deserves its place — or does it? Especially among newbies, C++ is commonly being hyped up to heaven — that is, until they fall flat on their faces and fracture their noses for being too cheeky. In the shadow of its popularity lie numerous flaws and caveats that will get even the most experience programmers hurt sooner or later. Frankly, it sucks — though the reasons why might not be entirely obvious.
To be honest, I don’t really have a problem with that language; I just don’t think it’s a particularly good one. Actually, it does suck quite a bit. Of course I do acknowledge that it has lots of features, that it lets you code the way you want to and that there is a compiler available for basically any system. That’s awesome, comes in extremely handy for the plethora of different situations we’ve to deal with and is probably the very reason why C++ is so popular and powerful. However, all of that only makes it a capable language, it doesn’t make it a good language — and here’s why.
Relics from the Past
In my opinion, C++ is this weird Frankenstein velociraptor that somehow survived the dark ages of programming and is now constantly being revived and patched up. The members of the standardization committee are trying hard to make things a little better by applying tons of makeup over its wrinkles. Obviously, this doesn’t work out too well since it’s still the same ugly beast under the mask. The problem here is that C++ is old — and I mean the antique kind of old that deserves to be put into retirement.
The project was first called “C with Classes” and later renamed to C++ in 1983. As its name now joyfully indicates via the postfix increment operator, it is supposed to be one step ahead of C (technically, by incrementing C and returning the old value. ++C would be correct, though that one looks dumb. ).
While the general concepts of object oriented programming that C++ derived from Simula (and not Smalltalk, contrary to popular belief) were already established, C++ combined the advantages of abstractions with the flexibility and performance required for low-level system programming.
Back in 1983, the programming environment looked entirely different — though this world has long changed. The circumstances aren’t nearly as bad anymore, yet the core of the language has been left untouched.
Relation between C & C++
There is a strong and obvious connection between the two, with C++ heavily extending the feature set of C. However, keep in mind that strictly speaking, C++ is not a superset of C, which is a rather common misconception. Although it is true that most (well-written) C programs will compile successfully under C++, there are certain constructs that are valid in C which will either produce a compile-time error in C++ or execute differently at runtime. The classic example would be memory allocation that requires an explicit cast in C++:
1 | int* block = malloc(sizeof(int) * 16); // Valid in C only. |
I do not work often with strict C compilers or even procedural C++ code, which is why I why I keep the C related part short. Many of the arguments here count for both C and C++, mostly due to their close bond with each other. I do think, however, that C is a much cleaner language, allowing you to maximize your code’s compatibility and portability without slaughtering readability too much. Then again, you can write FORTRAN in any language…
I wanted to point out here as a side note that Linus Torvalds made his infamous strong opinions on C++ clear once again in this pretty interesting, though not so political correct thread (not that anyone expects otherwise from him), stating:
C++ is a horrible language. It’s made more horrible by the fact that a lot of substandard programmers use it, to the point where it’s much much easier to generate total and utter crap with it. Quite frankly, even if the choice of C [for Git] were to do nothing but keep the C++ programmers out, that in itself would be a huge reason to use C.
I do not necessary agree completely with his statement, but one thing is just spot on: due to the high complexity of C++, it is definitely possible to “generate total and utter crap with it”. Those who have worked with C++ probably know that it is rather challenging to keep large projects well-structured and maintain a high level of readability.
Legacy Code & Compatibility
The de facto compatibility with C and its own backwards compatibility are both its blessing and curse. Bjarne Stroustrup put it nicely when he said, that the problem with C++ is that it cannot be cleaned up due to the tremendous amount of decade old legacy code that is still being used productively by businesses all over the world.
Generally, backwards compatibility is regarded as a positive trait of any language, which is definitely true. However, the unfortunate side effect is that the language is getting cluttered up with new features over the years that somehow need to work together with all this unnecessary and totally outdated stuff from the last millennium. Things like the primitive include system and the naïve preprocessor could be replaced by much more efficient and practical mechanisms. Doing so without breaking backwards compatibility is impossible, which is exactly why it will never happen.
Parts I Really Dislike
First I thought I had a lot to say about C++. Well, it turned out that this is actually not the case. I tried to come up with numerous bad things to mention here and made several notes over the past few weeks. Even though I could think of quite a few horrible parts, I had to hold back and reflect on each of them. I realized that all of these “features” suck for a very good reason — complaining or even ranting about them wouldn’t be justified. Unlike other languages (looking at you again, PHP) that just have been badly designed from the start, C++ is designed rather well given the circumstances, its purpose and its compatibility constraints. C++ is being developed and extended with great effort to make it as good as possible, while still keeping its legacy relics intact and putting all power into the hands of the developers to deliver a truly general purpose programming language.
Include System & Preprocessor
C++ does not have a proper module system that most modern languages provide. D, Java, C#, Python, Clojure, you name it. All of them got one. In combination with its primitive preprocessor, this usually results in a messy structure of header files and implementation files that rely on include guards to give basic protection against multiple inclusion.
God forbid if you have to deal with macro definitions that are scattered all around the source tree which likely cause unintended results after the macro expansion took place. Even small things like missing parenthesis around macro arguments or parameter specifications with additional operations can very quickly result in errors that may be difficult to track down:
1 |
|
Macros generally don’t behave like one would expect them to. The usual approach is to avoid macro definitions whenever possible and simply replace them with regular functions. Apart from other goodies like type-safety and better error handling, such functions will normally be inlined automatically by the compiler anyway.
Another downside I noticed is that the include system and the preprocessor give IDEs a hard time to figure out what belongs where and often cause them to deliver false results (auto-completion, navigation, code analysis, etc.). Kudos to the IDE developers though, I have noticed some major improvements during the last couple of years when it comes to the ability to effectively parse complex C++ macro definitions.
Overall though, having to rely on this system is just a mess and I wished there was a good alternative…
Undefined Behavior
What’s really unfortunate is that “undefined behavior” is part of the language and its specifications. Initially, some aspects were left undefined in order to be able to efficiently deal with different hardware and to avoid unnecessary overhead due to over-specification.
Do note that programmers often confuse the terms “implementation-defined” with “undefined behavior”. Parts of the language that are “implementation-defined” still allow the developers to write perfectly specified code as long as the target machine is known (see Stroustrup’s explanation).
Again, as it is with all the other bad parts, this will probably never be changed — it can’t be changed. Even Bjarne Stroustrup agrees in an interview over at MSDN that way too many things have been left undefined or implementation-defined. If C++ was redesigned from scratch, there would likely be no undefined behavior at all.
However, I don’t have a time machine and we just cannot break hundreds of millions of lines of code by picking a set of resolutions today.
Some of the semantic elements that have been left undefined involve even simple concepts like the execution order of function calls:
1 | foobar(foo(), bar()); |
It is not defined, whether foo()
will be called before or after bar()
has returned. That’s just silly.
Operations, such as division by zero, integer overflows or out-of-bound memory access will also result in undefined behavior. Granted, these things should not occur and can generally be avoided if the code offers even a slight hint of quality. However it would still be nice to have a well-defined behavior here, especially when talking cross-compiler and cross-platform.
Language Complexity
As a multi-paradigm system programming language, C++ is a complete beast when it comes to its feature-set and its specs, followed up with a steep learning curve and a very long path to mastery. It’s challenging to keep track of all its complex syntactical constructs that more often than not seem confusing and ambiguous.
Is it too complex? I don’t think so; there are thousands of programmers that speak C++ as if it was their mother-tongue. Its difficulty is clearly bearable, though I’d imagine that it is unnecessarily more difficult than it needs to be.
Within C++, there is a much smaller and cleaner language struggling to get out. […] And no, that smaller and cleaner language is not Java or C#.
Well, obviously it’s not Java or C#. These languages could never fulfill the demanding requirements of low-level hardware programming. Supporting even a fraction of C++’ native features would mean to extend their functionality considerably, converting them into an equal mess with similar problems.
However, it’s interesting to see some modern high-level features (e.g. lambda expressions, pure functions, …) being added to C++. I warmly welcome these changes, even though they do — again — add more complexity. I’m still waiting for that integrated garbage collector though that has been mentioned by Bjarne Stroustrup in 2000 in an interview with Slashdot.
So, what smaller and cleaner language could that be? What would it look like? The D Programming Language comes pretty close to what I’d expect from a modern language that shares its goals with C++. I briefly had the pleasure to take a look at D and I was amazed by its elegance and simplicity. I do not have enough experience to judge D, though I highly recommend checking it out.
Even though I agree that there is a concise language hidden in C++, whatever it might look like, I still strongly believe that it does not matter. This language will probably never see the daylight. C++ can’t be pushed away, it’s here to stay.
Conclusion
I’ve kept this article rather short and focused on the few points that bother me the most. I do think that C++ sucks, but as I’ve pointed out, it does suck for very good reasons. C++ turned out the way it did due to the numerous constraints and requirements that had to be respected and fulfilled. The main problem is that all the awful and outdated parts of the language have to be dragged along for all eternity in order to guarantee compatibility.
There are things in our society that mustn’t break, and most of them depend on software. And we need to consider our systems as a whole. Languages and their tool chains are part of it. We need to look into how we educate our developers and how we put emphasis on reliability [see interview].
With that in mind it should be easy to understand that C++ is a powerful tool that can be used to get almost any job done and it surely is the only language of this caliber. We’ll have to accept the fact that it still is an integral part of our profession and that it will stick around for many years to come — and that sucks.