I had a recent discussion on C++, as part of a wider topic of favourite and unfavourite languages. I was asked to give my opinions on it; in fact, I think I was asked to explain the motivation Higher-Level C. I’m not sure I really answered, except to say C++ was not my favourite language. This wasn’t pursued, but I’ve privately had a few further thoughts about why that might be.
My experience of C++ consists of:
- Doing a course on it, which was quite fun at the time. My final project was an assembler (i.e. a map from 3-letter strings to bytes) and virtual machine (i.e. a giant switch statement).
- Using it in one course at university, COMP 201 Software Engineering. The first half of the course was learning C++ and UML; this was tedious enough to risk aborting my resumed studies. The second half was using it to implement some data structures, such as hash tables. This was more fun, since I could just write C with methods instead of functions and classes instead of structs :-). I still had to use IO streams though :-(. The overall result of this course was that I’ve been turned off to the phrase “software engineering”; to this day I almost always prefer to say “programming”.
- Reading and tweaking it in various free software projects. I also read a lot of articles on “How to do awesome thing X with C++” on sites like Gamasutra.
- Using it for a few personal projects. All have been lost so hopefully you’ll tolerate me recollecting a few of them here.
- A multithreaded web server and web scripting language called ESP. This was in imitation of ASP, PHP, etc. An HTML page has snippets of code in it; the engine inverts this to give a program with snippets of HTML output; this is compiled and run.
- A CD ripper and audio signature generator. The audio signature part, which was largely number-crunching, was in C; C is almost an ideal language for that kind of work. But the GUI was in C++ using wxWidgets, which I still admire.
- A symbolic differentiator. Use the command
./differentiate "(x + z + 4)*(x^3 + x)*y" x, and it would spit out, well, whatever the answer is! I think this project from about 5 years ago was the last time I needed to know how differentiation works off the top of my head ;-). The expression was parsed using a hand-rolled recursive-descent parser, though I didn’t realise it was called that at the time. It could be transformed using the chain, product and other rules of differential calculus; simplified using various heuristics; and evaluated for given variable values.
- A galactic conquest game. The one great thing I remember implementing in this was a rotating, searchable 3D star map.
- Considering, at one point, whether to pursue a MSc in the topic of optimising a C++ Tutte polynomial generator.
- Using it at work for a large trading system. This C++ was written around 20 years ago, and comes with a third-party library — not the STL, but something similar to early STLs. There’s no Boost in use here!
So, I do have some experience of C++. (Not as much as I have of C or Python, though.) Unfortunately, I was never exposed to modern, idiomatic C++. I know about the STL and Boost through reading, but I’ve never used them.
Programming languages can be plotted on a plane with efficiency as one axis and high-levelness as the other. Almost all languages make tradeoffs between these competing design goals (this is a simplification and there are other tradeoffs to be made). The best languages are those along the frontier closest to the point of ultimate efficiency and ultimate high-levelness. And I think C++ is one of these; not quite as efficient as C, but a little higher-level. (I’m oversimplifying here, because C++ can be more efficient for some things.)
C++ has a number of high-level features that many a C programmer misses. Classes and virtual functions (which do away with the awkward redundancy for OO in C) are the big one. There’s also operator overloading, exceptions, and templates. There is some debate about how much the others should be used in practice, and it’s commonly said that “C++ programmers use 80% of the language; unfortunately each uses a different 80%”.
Another benefit is RAII. C++ does not have garbage collection, but it does provide the machinery to tell objects how to do most of their management. Want to use a complicated, expandable data structure in a function? Just put it on the stack, and rely on its deconstructor to do all the necessary cleanup! Smart pointers are another thing, though they are less used. One variety will keep a reference count for each pointer, and will destroy the pointed to object if that count is reached (say, when the last remaining pointer goes out of scope).
At a low-level, C++ is amenable to most of the compiler optimisations applicable to C. It has a particular other performance advantage: templates mean that optimum, highly-specific code can be generated from abstract, generic syntax. An example of this is an idiomatic sort routine: the syntax is the same whether you are sorting integers or objects, but where possible the simpler cases will be thoroughly optimised in place. The resulting code can outperform C, because the general purpose sort routine in C, qsort, needs an array to operate on and pointer to a comparison function that it will call on the elements of that array. Without major whole=program-optimisation (that includes qsort in its scope), there is no hope of inlining the comparison function with the sort routine and optimising away their interface.
Unfortunately, I find that C++ has combined high-level abstraction with all the brittleness of low-level C. It is still possible to cast objects to invalid types, or forget to destroy an object (or destroy one twice!). Additionally, compiler error messages can be amazingly baroque. This largely stems from templates: they are complex machinery that can make other code simpler — at least on the surface. Libraries like STL and Boost use them heavily. The result is that code using the library is short, simple, straightforward and efficient, because the template has done all the work of translating it into the best code for that case. They are immensely powerful. Famously, they are Turing-complete — but that’s almost certainly too powerful! Simpler generics would provide type-safe generic data structures.
If you write it perfectly none of this will be a problem. But when it comes to debugging, C++ can be a nightmare. Not only can you not tell what code has been generated for each line, but when you do find out you will likely have to dig through dozens of layers of abstraction to find where it’s actually going wrong.
So, except when I’m working on an existing program, I prefer to use C and Python for general programming. I probably use Python by default for most things. I resort to C if the problem is computationally intensive and/or is simple enough to solve in C (simple memory management, not too much string handling). Or, in fact, if I’m in the mood for some C work, which is more often than not. :-)
Finally, I’d note that a lot of great software that I use is written in C++. I’m often in awe of these applications and I can only assume their programmers have picked the best tool for the job in these cases.