Rules for Effective C++

I used to be a strong supporter of C++. It was the perfect language. In C++, if you want to influence how the hardware instructions are generated, you can do that. If you want to program without pointers and without caring about how memory is allocated, you can do that.

Recently, however, my views have changed after reading Scott Meyer's book, Effective C++. In Meyer's book, he goes through every feature of C++ and shows you how you have to program with extreme care to avoid undefined behaviour. It seems like every modern feature that C++ has was specifically designed to help you shoot yourself in the foot.

I never realized this before, because I simply never use these dangerous features. In this article, I'll show you how to program in C++ safely.

C++'s Broken Exceptions

Take exceptions, for example. Many programmers will tell you that they are a great idea. They let you indicate errors when creating object, and avoid making you check return codes. You can handle errors in the areas of the code that is prepared to handle them.

What you may not know is that using exceptions in C++ makes a lot of code unsafe. In effect, it means that you cannot use pointers. Take this code, for example:

void foo()
{
    MyObject* obj = new MyObject();

    bar();

    delete obj;    
}

If you are a C++ programmer and you use exceptions, you should see the obvious memory leak. If bar() throws an exception, or calls any function that throws an exception, then obj will not be deleted.

Steve's rules for effective C++

I have been programming in C++ for a decade, and I never realized these flaws until I read Meyer's books. I find C++ to be just fine, and the reason for that is because I program in a style that doesn't involve these pitfals. Here's how you can program in this way too:

Avoid exceptions

Exceptions will only leave you open to the memory and resource leaks. Don't use them. The exception to this rule is if you are programming in a style that doesn't use pointers, and everything is encapsulated into smart pointers.

Constructors should do nothing

Constructors have no way of returning an error code (unless you use exceptions, which are bad). That means that your constructors shouldn't do any real work. Don't try to open up a database connection, or call any functions that could fail. Constructors should be used only to initialize data members.

Use copy constructors sparingly

Copy constructors are very error prone, because they are another thing that you have to remember to change if you add a data member. You're much better off if you don't allow copying at all. Just pass pointers around. If you must pass by value, then don't put anything in your object, like pointers, that will require special handling. That way, you can use the automatically generated copy constructor. Unlike you, the compiler will never forget anything.

Use malloc or new without checking the result

I used to write programs that checked every call to malloc() and new() for failure. In Microsoft C++, the new() operator will actually through an exception if it fails, so checking for NULL is useless anyway. Today's machines have gigabyes of memory, and you don't need to verify every call to malloc() or new().

It's actually quite hard to induce these functions to fail, so even if you did handle their failure, you probably wouldn't test it. Do you really want to be releasing code that you haven't tested? There are cases where it would be better for your program to crash, then to continue to operate in an undefined state.

However, there are times where I would check whether a memory allocation failed:

  • When you are allocating something that is several megabytes, like space for images or files. In this case, it is quite possible for the allocation to fail if the user has opened up too many files in your program.
  • When you are programming for a nuclear reactor, or space shuttle. Also, a missile guidance system would be acceptable.
When you are programming for an embedded device, however, it might be beneficial to not check the return code of malloc. This is when there should be enough memory in the heap for all operations. If your process silently fails when memory allocation fails, you might never catch a memory leak that is exhausting your heap. It is much better to fail catastrophically by trying to use the NULL pointer than silently failing.

Comments