In my opinion, the dangers of C++ are somewhat exaggerated.
The essential danger is this: While C# lets you perform "unsafe" pointer operations using the
unsafe keyword, C++ (being mostly a superset of C) will let you use pointers whenever you feel like it. Besides the usual dangers inherent with using pointers (which are the same with C), like memory-leaks, buffer overflows, dangling pointers, etc., C++ introduces new ways for you to seriously screw things up.
This "extra rope", so to speak, which Joel Spolsky was talking about, basically comes down to one thing: writing classes which internally manage their own memory, also known as the "Rule of 3" (which can now be called the Rule of 4 or Rule of 5 in C++11). This means, if you ever want to write a class that manages its own memory allocations internally, you have to know what you're doing or else your program will likely crash. You have to carefully create a constructor, copy constructor, destructor, and assignment operator, which is surprisingly easy to get wrong, often resulting in bizarre crashes at runtime.
HOWEVER, in actual every-day C++ programming, it's very rare indeed to write a class that manages its own memory, so it's misleading to say that C++ programmers always need to be "careful" to avoid these pitfalls. Usually, you'll just be doing something more like:
Foo(const std::string& s)
This class looks pretty close to what you'd do in Java or C# - it requires no explicit memory management (because the library class
std::string takes care of all that automatically), and no "Rule of 3" stuff is required at all since the default copy constructor and assignment operator is fine.
It's only when you try to do something like:
Foo(const char* s)
std::size_t len = std::strlen(s);
m_name = new char[len + 1];
Foo(const Foo& f); // must implement proper copy constructor
Foo& operator = (const Foo& f); // must implement proper assignment operator
~Foo(); // must free resource in destructor
In this case, it can be tricky for novices to get the assignment, destructor and copy constructor correct. But for most cases, there's no reason to ever do this. C++ makes it very easy to avoid manual memory management 99% of the time by using library classes like
Another related issue is manually managing memory in a way that doesn't take into account the possibility of an exception being thrown. Like:
char* s = new char;
/* ... */
some_function_which_may_throw() actually does throw an exception, you're left with a memory leak because the memory allocated for
s will never be reclaimed. But again, in practice this is hardly an issue any more for the same reason that the "Rule of 3" isn't really much of a problem anymore. It's very rare (and usually unnecessary) to actually manage your own memory with raw pointers. To avoid the above problem, all you'd need to do is use an
std::vector, and the destructor would automatically get invoked during stack unwinding after the exception was thrown.
So, a general theme here is that many C++ features which were not inherited from C, such as automatic initialization/destruction, copy constructors, and exceptions, force a programmer to be extra careful when doing manual memory management in C++. But again, this is only a problem if you intend to do manual memory management in the first place, which is hardly ever necessary anymore when you have standard containers and smart pointers.
So, in my opinion, while C++ gives you a lot of extra rope, it's hardly ever necessary to use it to hang yourself, and the pitfalls which Joel was talking about are trivially easy to avoid in modern C++.