C++ gets surprisingly little love these days. Some say it’s too low-level (the absence of garbage collection seems to be a popular complaint). Others say it’s not low-level enough. Again others claim it’s clumsy, bloated, and overly complex.
As you may have surmised from the title of this post, I do not agree. C++ is by far my favourite programming language. It’s not just that C++ is particularly well-suited for the projects I am currently using it for (Timetable Generator, and the backend of Project Flanders), although this is certainly true. More generally, I find C++ a pleasure to program in, more so than any other language I’ve used.
I will concede this much: if you don’t know what you’re doing, C++ is probably easier to misuse than other languages. However, in the hands of the experienced programmer, C++ can be a very versatile and powerful tool.
This post is the first of a series about programming in general and C++ programming in particular. The main purpose of the series is to share with you some of the things I’ve learned over the past few years, but I also hope to demonstrate the power of C++ in the process.
For now, I will start with an overview of why I hold C++ in such high esteem. I will try to stick to the high-level ideas, because I could go on and on about the specifics.
- C++ combines the performance of C with the expressiveness of higher-level languages like Java. Many people see choosing a language as a tradeoff between performance and expressiveness. The prototypical choices are C on the one hand (fast because it’s close to the machine, but lacking high-level constructs that facilitate abstraction, like classes, inheritance, polymorphism, and generics/templates) and languages like Java, C#, or python on the other hand (slower because they are interpreted or run through a virtual machine, but having these high-level constructs). In C++, there is no tradeoff – you have both the performance of C, and the convenience of high-level features. Unlike Java, C#, or python, C++ does not impose a performance overhead for high-level features.
Consider garbage collection as an example, a feature shared by Java, C#, python, and many other “modern” languages. Garbage collection makes memory management much easier because it saves you from having to explicitly deallocate the memory that you have allocated. However, garbage collection invariably incurs a performance overhead in the languages that support it. On the other hand, in C++ you can use certain techniques (such as RAII) to achieve a level of automatic memory management that’s just as convenient as garbage collection, but with almost no overhead. Moreover, the C++ techniques for memory management generalize to the mangement of other resources, such as files, locks, and threads.
- C++ has a strong, static type system, which means more errors get caught at compile time. Whenever I program in a dynamically-typed language such as python, I always marvel at how many mistakes I make that don’t get caught until the program is run. They are almost invariably the types of mistakes that in C++ would get caught at compile time. C++ makes your programs safer by catching errors as early as possible.
The argument I made so far works in favour of any statically-typed language. However, C++’s type system has some features that set it apart from the type systems of other statically-typed languages like Java. First, C++ lacks the ability to do some type-unsafe things, like cast every object to type “Object” (OK, technically speaking, you can cast any pointer to void* in C++, but doing this is discouraged, while casting objects to Object is done routinely in Java). Second, C++ allows you to test a greater variety of conditions at compile-time (for example through static assertions and const-correctness).
- C++ can be used to program in many different programming styles or paradigms. There are different programing paradigms, such as procedural programming, object-oriented programming, generic programming, and functional programming. Each has its strengths and weaknesses, and each is suitable for different purposes. Most languages are designed with one programming paradigm in mind, which means that if your problem domain has some areas that are suited for one paradigm, and some that are suited for another, you end up either using a language in a way that it wasn’t intended (which leads to awkward, hard-to-understand code), or using different languages for different components (which leads to repetition of common code). C++, on the other hand, is a multi-paradigm programming language that allows you to mix styles within the same program, so that you can use the style most suitable for each component, while staying in the same language.
I will give many examples that illustrate the power of C++ over the course of the next few posts, but as a teaser, I’ll start with the example that made me go “WOW”: Boost.Xpressive.
Do you use regular expressions? Do you ever think about what happens behind the scenes when you use a regular expression? What you type is a string representation of the regular expression. This string representation needs to be parsed, checked for syntax errors, and converted into an internal representation that can be used for fast matching (a finite automaton, as you might recall from your college theory courses). This process is called compiling the regular expression, because in many ways it is analogous to compiling source code into an internal representation that is suitable for fast execution (machine code).
Compiling a regular expression is expensive, yet in most languages, it needs to happen every time a program that uses a regular expression is run. If the regular expression is known at (the program’s) compile time (which most of the time it is), wouldn’t it be nice to be able to compile it at compile time, and check it for syntax errors at compile time?
Boost.Xpressive does exactly that. Instead of specifying a regular expression as a string, you specify it using C++ syntax elements that resemble the regex syntax. For example, to match a sequence of one or more letters or numbers, you would normally use the regular expression string:
In Boost.Xpressive, you instead use the following (non-string) construct:
+( _w | _d )
Here, _d and _w are special regex constants, and the operators | and + are overloaded to build compound regexes from their operands. The syntax isn’t exactly the same (we are constrained by C++ syntax rules such as no backslash in identifier names, and the absence of a postfix + operator), but it is similar enough to be recognizable.
Here’s the punchline: in the process of compiling this line of code, the C++ compiler actually performs the compilation process for the regular expression, including syntax checking, and the only thing that goes into the compiled code is the final, internal representation of the regex. I invite you to show me another language that can do that!
Finally, a note on complexity. Writing a library like Boost.Xpressive is certainly not trivial – it involves advanced techniques like expression templates and template metaprogramming (both of which I’ll blog about in the future). But using the Boost.Xpressive library is not much more difficult than using regular expressions in other languages. And this brings us to perhaps the most distinctive property of C++: its core language features are versatile enough that many facilities that require language support (i.e. changes to the compiler, interpreter, or virtual machine) in other languages, can be implemented in C++ as a library. Just look at the Boost.Lambda library, for instance, which implements lambdas (anonymous functions constructed on the fly) without any language support (no, it’s not perfect, but it goes much further to emulate built-in lambdas than would be possible in other languages). When these libraries are well-designed, they can be just as easy to use as the corresponding built-in facilities in other languages. Of course, implementing such libraries is difficult, but so is implementing the built-in language features of other languages. The key difference is that extending a language with built-in features requires widespread concensus among the language’s community of developers, the cooperation of the entity in control of the language (e.g. Microsoft in the case of C#), and often a lengthy bureaucratic process. On the other hand, anyone can feel free to write a library that effectively extends the C++ language.
I hope I’ve given you a taste of the power and expressiveness of C++, and convinced you to at least keep an open mind when you hear people bashing it. I think it’s only fitting that I end with a quote by Bjarne Stroustrup, the creator of C++:
“There are only two kinds of programming languages: the ones people always bitch about, and the ones nobody uses.”