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:
-
The following C++ statement is invalid in C.
x = a ? b : c = d;
Why? How do you fix it?
-
The following C statement is invalid in C++.
int *a = malloc(sizeof(*a));
Why? How do you fix it?
-
The declaration:
void some_function();
is valid in C and C++. Does it mean the same thing in both languages?
- 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".
- C performs an implicit conversion for void *, while C++ does not. Use an explicit cast to work around this.
- 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-prototypeswill result in a warning; to suppress them, placevoidwithin the parentheses.
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 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.
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.
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.
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.
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.
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.
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.
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.