C++ is complex. Very complex. Some say that it is far too complex, and I'm going to neither agree nor disagree with that here. That's not what this post is about.

I was linked earlier on to a presentation entitled "The Dark Side Of C++", written in August 2007 by C expert Felix von Leitner. He attempts to explain C++'s pitfalls and demonstrate why a programmer should choose another language. Though the intention is sound and some good points are made, unfortunately it also contains untruths. Therefore, I shall go through parts of the presentation, debunking its lies.

Again, I'm not doing this to defend C++. I'm actually doing it to defend any language newcomers who may stumble across this dangerous misinformation and believe it to be true, because it is not.

I am not going to go out of my way to point out areas that I agree with. (In particular, the implementation of library features does lead to some utterly horrendous diagnostic output.)


Slide 7

This slide is entitled "ever-changing standard", then lists the following ways in which the standard allegedly changed:

  • Wrote commercial C++ app in 1997
  • Then C++ changed (life time of i in "for (int i=0; …)")
  • Then C++ changed again (use iostream instead of iostream.h)
  • Then C++ changed AGAIN (namespaces)

Useless maintenance work. Would have been less trouble in C.

In fact, C++ was standardised in 1998, and all of these elements were fixed in place at that time. To this day, none of them have ever changed in the standard. The "useless maintenance work" would have been less trouble had Leitner actually written standard C++.

Slide 8

Old and busted:
for (int i = 0; i < n; i++)
New hotness:
for (int i(0); i != n; ++i)

Convention and idioms are important. If we all agree basically how to code, then cross-maintenance becomes easier.

However, this difference in construction syntax never really took off. Both are valid, and there is no consensus at all that the second approach is the "new hotness"; in fact, I don't think that I've ever seen it in production code.

Slide 12: "C++ is hard to parse"

struct a{typedef int foo;};struct a1:a{};struct a2:a{};
#define X(b,a) struct a##1:b##1,b##2{};struct a##2:b##1,b##2{};
X(a,b)X(b,c)X(c,d)X(d,e)X(e,f)X(f,g)X(g,h)X(h,i)X(i,j)X(j,k)X(k,l)
X(l,m)X(m,n) n1::foo main(){}`

I'm not convinced that you can just throw up a ridiculously retarded piece of code like this, and use it as proof of a language flaw. When would you write this? Well-written C++ code is reasonably easy to parse (sometimes).

Slide 15: "C++ is hard to write"

  • Can't throw exceptions in destructors, shouldn't in constructors

You can throw exceptions in destructors (though you shouldn't).
Throwing in constructors is fine; the default allocator throws bad_alloc, for goodness's sake.

  • Initializers done in order of declaration of fields in class, not written order

Good. I can't imagine having to figure out where the implicitly-constructed ones would go in an incomplete member-initialiser were the order not concretely defined.

  • auto_ptr is useless

I can't disagree with that, and in fact auto_ptr has been deprecated in C++0x.

  • Iterators don't know anything about the container, can't detect errors
  • For a vector, at() does bounds checking, but operator[] doesn't
  • Can't call virtual member functions from constructor or destructor

Again, these are all good things.

Slide 16: "Throwing exceptions from con/destructors"

My personal favourite, this slide is full of utter nonsense.

  • Exceptions in constructor don’t unwind the constructor itself
  • Does not even clean up local variables!
  • Must do own cleanup

This is a flat-out lie. When throwing from a function, even a "special function" like constructors, any local objects are destroyed just as they would when going out of scope anywhere else.

#include 
#include 

using std::cout;
using std::logic_error;

struct Tracked {
   Tracked() { cout << "*"; }
  ~Tracked() { cout << "~"; }
};

struct Test {
   Test() {
      Tracked t;
      throw logic_error("Yes, it *is* unwound. Things go out of scope as usual.");
   }
};

int main() {
   try {
      Test t;
   }
   catch (...) {}
}

// *~

Live demo.

  • OTOH: no other way to return failure, constructors are void

Constructors are not void. They are constructors.

Slide 20: "Bounds Checking"

  • For a vector, at() does bounds checking
  • operator[] can, but does not have to
  • The one from gcc doesn’t

Good. Then I have an option when I don't need the overhead of bounds checking. C arrays don't have bounds checking.

Slide 21: "Virtual functions in con/destructor"

  • In the constructor, the vtable is not properly initialized

That would be the virtual pointer. The vtable exists since compile-time. This is an implementation detail though: C++ as a language has no knowledge of either.

  • Usually, function pointers in vtable still point to virtual member of base class
  • ... which can be pure virtual

This is a lot more useful than the automatic this pointer referring to an object that does not yet exist. Remember, when the Base part is being constructed, the Derived part hasn't started being constructed yet.

  • In the destructor of the base class, vtable points to base class

Similarly, it would be incredibly awkward to work around a virtual dispatch mechanism that tried to use the already-destroyed part of an object as you're cleaning it up.

Slide 37: "Other Nitpicks"

  • Can't tell what exceptions a library can throw

You can, by looking at the documentation. This is no different from the manner in which you find out anything else about library functions.

Slide 39: "Exception Safety"

If new[] throws an exception, this leaks a file handle and deadlocks the next caller.

That's only because Leitner is writing C-style code. He has not used the principle of RAII and insists on using antiquated C library functions rather than file streams, and this is the cause of his problem. Using C++ conventions and idioms completely nullifies this issue.

Slide 47: "Pointer arithmetic with base classes"

This slides presents an example of attempting to use non-polymorphic classes polymorphically. Just don't do that then.

Slide 48: "Gratuitous Bjarne Quote at the end"

Whole program analysis (WPA) can be used to eliminate unused virtual function tables and RTTI data. Such analysis is particularly suitable for relatively small programs that do not use dynamic linking.

No shit, Sherlock.
It will be ready right after Duke Nukem Forever runs on the Hurd.

Microsoft Visual Studio has supported this since at least two years before the publication of this presentation and, as far as I'm aware, the recently released Duke Nukem Forever is not yet available on the GNU Hurd platform.


Now, don't get me wrong. As I said before, Leitner makes some valid points, and the overarching message that C++ is overly complex and confusing in certain places is certainly not without merit.

So go ahead and read his presentation... just keep a copy of this article with you throughout.