C++ versus C

Originally C++ was envisioned as a superset of C: the majority of existing C programs should also be valid C++ programs. The standards have since diverged substantially, but even in the old days there were obscure exceptions:

Quiz

  1. The following C++ statement is invalid in C.

    x = a ? b : c = d;

    Why? How do you fix it?

  2. The following C statement is invalid in C++.

    int *a = malloc(sizeof(*a));

    Why? How do you fix it?

  3. The declaration:

    void some_function();

    is valid in C and C++. Does it mean the same thing in both languages?

Answers

  1. The C++ grammar for the ternary operator differs subtly from the C grammar, allowing an assignment expressions as the last term. For C, we must parenthesize "c = d".

  2. C performs an implicit conversion for void *, while C++ does not. Use an explicit cast to work around this.

  3. In C, it declares a function with an unknown number of arguments, while in C++, it declares a function with zero arguments. Compiling such C code with gcc -Wstrict-prototypes will result in a warning; to suppress them, place void within the parentheses.

C Reloaded

Like many movie sequels, C++ contributed interesting ideas, but slipshod execution and haphazard direction doomed the result. The original is still the best.

Valuable innovations include // comments, inline functions, variables local to for loops, and namespaces. Most of its other features are detrimental.

Templates

Templates appear useful, but are overly complex. Compliation is slow, partly because of bloat: templates generate code for every instantiated class. This bloat can lead to slow run times. Error messages are cryptic. Mixing inheritance and templates gets tricky. Additionally, we must be aware of another form of overloading.

Athough templates are Turing-complete, we’re better off using a language with comprehensible syntax. Also, a programmer exploiting template metaprogramming must be conscious of 3 languages in one file: templates, C++, and the preprocessor.

No point of reference

References can be dangerous, as one can no longer assume f(x) only reads from the variable x. Their utility is questionable as the size 1 array trick mostly eliminates the "." versus "->" annoyance.

Misinformation hiding

C++ seems to automate and hide the opposite of what it should. For example, garbage collection might be a useful feature, but remains unaddressed, while it might take hours to unearth buggy code buried deep in a class hierarchy in a copy constructor.

Overloading overload

C++ has function overloading, subtype polymorphism, implicit casting, and template specialization. How do these interact? Given a line of code, which of these are in effect?

I have mixed feelings about operator overloading. On the one hand, it is extremely natural notation for mathematical data structures, but on the other hand, I’m accustomed to mentally mapping arithmetic operators to machine instructions.

Complicit casting

Implicit casting is a flaw of C, and C++ chose to preserve it. On top of this, C++ adds a cast syntax that resembles a function call, along with 4 new cast operators the programmer must learn. A constructor with one parameter can easily be misused via casting, so much so that the explicit keyword was introduced.

Objections to objects

Objects were the main motivation of C++, but unfortunately have turned out to be its greatest misfeature.

Constructors and destructors are a nuisance. As constructors cannot return a value, they should be simple functions that never fail, hence often a initialization function is required anyway. Additionally, variable declarations lose their innocence: one may have traverse far up a class hierarchy to determine what work is being done. Furthermore, global objects call their constructors in an unspecified order.

The compiler-generated copy and assignment constructors are almost always unwanted, and can also make cheap-looking operations deceptive.

The private and protected mechanisms for separating interface from implementation are inferior to using file scope. Typically, implementation details reside in the private or protected sections of a header file, polluting the definition of the interface and violating the principle of information hiding. It is too easy for a programmer’s eye to notice undocumented implementation details, and subsequently write code relying on them. Moreover, changing the implementation requires modifying the header file, which in turn requires recompiling all files that include it.

Non-technical advantages

We’ve been assuming the goal is to code efficiently and effectively. If one’s intentions are less honourable, then the weaknesses of C++ become strengths. For example, compilation is slow, and triggered by the smallest of changes. This can be exploited to increase free time at work. Obfuscating code is trivial, and C++ compilers are notorious for portability and interoperability problems, improving one’s job security.

Further reading

I could go on, but would rather simply cite a few links on this subject.

Google’s C++ Style Guide has some overlap with the above, but is less extremist.

The UNIX-HATERS Handbook denigrates C++ with more flair and gusto, as can be seen by some of its section titles: "The Assembly Language of Object-Oriented Programming", "The COBOL of the 90s", "C++ Is to C as Lung Cancer Is to Lung". Highly recommended.

Yossi Kreinin maintains the C++ FQA (Frequently Questioned Answers) Lite, the best critique of C++ I have seen. He exposes many of the language’s crimes against computer science. For example, did you know its grammar is undecidable? Or that operator overloading is sabotaged by at least 3 design decisions? His main conclusion is inescapable: "there is no reason to use C++ for new projects".

Linus Torvalds posted a strongly worded criticism of C++ to a mailing list.

Redemption

In recent years, my stance has softened mainly because of two features:

  1. Type inference can reduce boilerplate signifcantly.

  2. Lambdas. Better late than never.

I also applaud other paperwork reduction features such as range-based for loops and new syntax for literals. Now if they could only replace implementation inheritance with Haskell-style type classes!


Ben Lynn blynn@cs.stanford.edu