Why C++ is not my favourite language (by )

Yesterday, I had a discussion with somebody who's a big fan of C++. He spoke of C++ as letting you write high-level code (with smart pointers, exceptions, destructors and templates conspiring to smartly remove lots of the normal drudgework of C programming), while still being able to perform low-level memory access like C if you want to, and getting highly optimal code. He admits that C++ is complex and takes a lot of time to master, but that you can produce highly elegant and compact code once you've mastered it.

But I think this is broken.

The cost is that unless you've mastered C++ you may run afoul of the many caveats of the language, which make it very easy to unwittingly drift into "unspecified behaviour" which your compiler will accept, and may even work most of the time, but may go haywire at run time. Getting the best out of the language requires a lot of wizardry, as I have discussed in the past. So although it's a fun intellectual game to master C++ and then show off your amazing new template metaprogramming tricks, in practice, to get software written, you really want a language that people can master easily; so they can spend their time learning about the problem domain rather than learning a complex language. I think there's better ways of getting very clever people working for you than requiring them to absorb a language that only wizards can truly master; it also makes it very hard to hire an intern to do some simple stuff.

Garbage-collected dynamic languages with macros such as Scheme and Lisp have all of the high-level-coding benefits of C++, and then some more (continuations, closures, and macros written in the same language as the rest of your code come to mind), with the difference that they are a lot simpler to learn (and don't make it incredibly easy to shoot yourself in the foot).

But they're slower, aren't they? C++ lets you write a complex recursive template that generates a class that turns out to just have a single int field and no virtual methods, and hey presto, you'll get a single-word object that can be carried around in a register, without being allocated on a heap or even on the stack, and it'll statically deduce the correct code to invoke when you call a method on that object, and it'll even inline it there and then.

While the equivalent in, say, Scheme would involve lots of indirection through heap-allocated objects and all that; because values in dynamic languages can be anything, of any type, the runtime always has to deduce their type from flags of some kind in the values, then do a load of expensive branching and indirect calling to get to the correct code.

THIS IS LIES! LIES, LIES, LIES!

Only a simple implementation has to work that way. Slava Pestov's work on Factor, for example, has produced a compiler for a very dynamic language that produces some really good code. By inferencing values and types, it can inline code that in C or C++ would have to be indirected through a function pointer. It can deduce the types of values in certain contexts, and then not need to 'box' them by adding extra bits to record the type; then operations like addition can just compile to a single native add instruction, rather than the long-winded chain of type tests, un-boxing, addition, and re-boxing of the result. And mechanisms like Scheme's SRFI-4 homogenous vectors and Factor's struct classes and struct arrays let us have efficient unboxed bulk data structures.

So the dynamic world is, through clever compilers and data structure libraries, able to approach, and maybe one day exceed, the performance of code generated by C++ compilers; C++'s "speed" is the result of forcing the programmer to be aware of low-level details; which makes it easier for a compiler to generate fast code (and harder for the programmer to write correct code).

Dynamic languages are a lot simpler to learn than C++, and much easier to write a working implementation of, and have much less cope to accidentally produce code with undefined behaviour; but they have more potential for smart optimisation in the compiler than C++. And with rising interest in dynamic languages, people are starting to implement more and more of these optimisations.

9 Comments

  • By Samuel, Fri 28th Aug 2009 @ 5:14 pm

    While I appreciate your views, sometimes having something statically checked at compile time is very helpful. Many modern dynamic languages let you shoot yourself in the foot without realising it: you are required to test all code paths to ensure you didn't do something stupid like add an integer to a string..

    C++ may have problems, but saying that it is less likely to produce code with undefined behaviour... I'm not sure if this is a strong argument for using a dynamic language.

  • By Andy, Fri 28th Aug 2009 @ 5:43 pm

    Is now the right time to start a campaign to move people off C++?

  • By alaric, Sun 30th Aug 2009 @ 8:55 am

    @Samuel: An interesting point, but I disagree 😉

    Dynamic languages tend to have more defined behaviour; in C++, there's lots of things you can do (such as making up a random memory address to store something at by, for example, forgetting to initialise a pointer) that might just work (the bit of memory you use exists and isn't used for anything else), or might bomb instantly (memory isn't mapped or correctly aligned), or might appear to work but then something totally unrelated is broken (because you corrupted its memory).

    Now, static typing can indeed help detect bugs; dynamic language testing certainly relies more on high-coverage testing. But at least if a test case passes in some dynamic code, you can be confident that this code now works (at least for the test cases you've thought of). While C++ code might compile and run purely out of blind luck!

  • By alaric, Sun 30th Aug 2009 @ 9:02 am

    @Andy: Well... I dunno.

    Computing being a young field, there's still a lot of people doing things that aren't the best thing for them to be doing, because they don't know the consequences. Many of the consequences can only be found by experience, and the rate of propagation of experience in our field is (even with the Internet) still slow compared to the rate of growth of the field (in terms of new programmers appearing). So lots of people are having to make the same mistakes.

    And yet the field is improving, as good ideas do sometimes reach a critical saturation, such that newcomers encounter them promptly, and then they're around for good.

    So I think the thing to do is to be loud whenever you find a better way of doing something new, so that people can see this better way, ideally before they become too settled into doing things in a certain way (which then blinds them to the value of new ways, and gives them a fear-of-the-unknown hurdle to overcome).

    I think that trying to move people who have settled on C++ off would be a lot of work for little gain; but making sure new programmers get to taste the alternatives before settling on a choice is relatively easy 🙂

  • By Eric, Mon 31st Aug 2009 @ 6:22 pm

    As a developer who started out doing Assembly Lauague work, then C work, then C++ work for 10+ years, and for the last five mainily C# work.

    I can say that C# is a language with 90% of the advantages of C++ while removing the 10% of the C++ lanuage that causes the most problems.

    One thing I don't miss is having to create a header file for each class I need to create. There are no header files forced upon C# developers 🙂

  • By Paul Norman, Mon 31st Aug 2009 @ 6:32 pm

    I am not familiar with Scheme or Factor, so I cannot comment on how well they do things. We have an application for engineers that has computationally intensive parts. When we tried to move the application to C#--mostly done by one who had programmed about half the C++ version--the computationally intensive parts were so slow, the engineers hated it and went back to the C++ version.

    I will certainly welcome a more straightforward way of doing things that is FAST when it becomes available. I do not consider hybridizing Fortran (for speedy calculation) and some other language (for GUI, etc.) as very straightforward, however. C++, using smart pointers and templatized containers such as std::vector, works pretty well for what we do and it is not all that hard.

  • By Frank, Tue 1st Sep 2009 @ 12:38 am

    Arguments like these totally miss the point of why people use languages like C++ and Java. It really does not matter how elegant the language is when in comes to the practice of programming. What most programmers want is a language that enables them to get things done. That requirement means that first and foremost the language needs to have a model of computation that they understand. More people find the imperative model easy to understand than find lambda calculus easy to understand. That means that for the majority of programmers the functional style is significantly harder to use. You can take a language like C++ and make it easier by restricting the scope of the features Features can be learned one by one. A language is useless until you understand the computational model. That makes the barrier to entry for Scheme, Lisp or Haskell huge. For some people (I know the functional types will find this incomprehensible, but even for some good imperative programmers) the barrier is nearly insurmountable. Now you may say that those people must not be good programmers and should find another profession, but the reality is that I have known people that were perfectly good programmers in C++ or Java, who have told me that they all but failed in an attempt to learn a functional language. The reality is that Lisp has been around for ever and is still not in common use. There is a reason for that and it has nothing to do with the elegance or power of the language.

  • By Max, Tue 1st Sep 2009 @ 7:55 am

    This article shows difference between practice of programming and idle talk of programming . Professional would think: "how to solve the task on C++", but dilettante prefer to read or think: "why this task cannot be solved on C++"

  • By Tim, Sat 21st Nov 2009 @ 10:56 am

    Frank, I couldn't disagree with you more on almost everything that you mentioned. Most of the languages you mentioned are not particularly "functional", (eg. not only does almost every language you mentioned allow for explicit side effects, but many of them even have iterative looping constructs and the same syntactic sugar that most oop languages would have) and saying that you have understand the lambda calculus to code in lisp is as absurd to say as mastery of c requires understanding of a turing machine. Lisp was unsuccessful, but not because it was hard to grasp, (if that were the sole criterion, C++ would have been dead and gone from the get go) it was unsuccessful for several reasons:

    -Prior to 15-20 years ago, computers weren't fast enough for garbage collected languages -It's syntax is too alien for most people

    As for Haskell, no argument about it being a radical change, but it's still easier (and much easier to code in) than C++ to learn. (no joke, personal experience). Haskell is not really very hard to learn other than that there are not very many applications written in it.

    C/C++/Java are massive success stories primarily for the following reasons: 1) C was successful because it was portable and efficient, no other language dominated, and structures seemed to get around the incredibly limited type systems of it's era 2) C++ was successful because it took advantage of an already huge user community and through compatibility with C++, and it's current main value, like with C is that tons of really useful code is written in it. I actually would agree with C++ proponents that it's mix of high and low-level concepts can make it useful in a variety of situations.

    3) Same thing for Java, it made incremental improvements on C/C++, and was carefully marketed towards the existent community. It's performance is every bit as abysmal as Lisp/Scheme, the reason why it wasn't an issue for Java is that in the mid 90's finally memory was cheap enough that it was no longer an issue. (plus riding the OOP hype train helped a lot)

    As for functional programming being "unsurmountable", I would seriously doubt that. If most of the jobs today required FP overnight, everyone and their mother would be learning and using it. I don't think that people who couldn't learn it are dumb, rather that first of all, not only can they learn it, (because it is not difficult to learn at all in spite of what you say) but it's more likely a psychological perception than anything else.

    I can only say that on a personal level when I was beginning programming I started with C++, got burned out, and was turned on to programming several years later by scheme. Believe me, I found it to be significantly easier to learn. I'm not saying that C++ is too hard to learn, (again, that's an absurd argument) more that it is probably not a good choice for a first language.

    Another thing I think is that part of the reason why the languages today are so uniform is because now CS only people dominate the industry, whereas before, math and electrical engineering folks were the primarily authors of software. So maybe things are getting a bit inbred. I think their is a psychological perception of what is "easy" that distorts things, and a perception that a language like scheme or common lisp requires anything beyond basic algebra is rather misleading.

    Finally, to the author, I'm not sure I would agree if dynamic checking is really the way to go for everything, even though I love scheme to death, static typing along the lines of Haskell seems to be much more what I wanted in the first place. (and it subsumes virtually any advantage of OOP as far as defining new "types")

Other Links to this Post

RSS feed for comments on this post.

Leave a comment

WordPress Themes

Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales
Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales