Effective C++ is a book published by Scott Meyers that is currently in its third edition. The first time I heard of the book was when I toured a gaming company in London Ontario. The company in question, purchases a copy for every new designer and makes it mandatory reading. The book is not a “how to program in C++” book but rather a guideline for designing C++ code that is supportable and reliable. The subtitle of the book is “55 specific ways to improve your programs and designs”.
One of the things that Scott is trying to avoid in C++ programs is something called “undefined behaviour”. This means that the executing program will not always fail, may write into memory where it shouldn’t or could even scribble on a hard drive. It really means “undefined behaviour”. I wanted to define this here because as I describe the 55 guidelines, this will be mentioned many times.
The author is careful to explain that although he has 55 rules, not all of them will apply in all cases. C++ has been around for a long time and is used in many different ways. Not all of the extensions are used by all designers and some of the rules are specific to extensions like TR1 and Boost.
I realize that many of the readers of this BLOG are not C++ designers, but some are and have requested that I do a technical post every now and then. I’ll try not to string too many of these together in a row and will break up the rules into a series of posts.
What follows is my take on the first four rules:
1: C++ is a Federation of languages: and as such the rules may apply differently depending on your programming environment. C++ = “C” + “C-With-Objects” + “Template-C++ ” + “STL”. An example of why this is important would be ‘pass-by-value’ is generally more efficient than ‘pass-by-reference’ for C, but as you move into object oriented implementations of C (that contain constructors and destructors), ‘pass-by-reference’ is the better choice.
2. Consts, enums and inlines are better than defines: This has more to do with deciding between the pre-processor or the compiler. If you use a define, the pre-processor may replace the variable name with the value and the variable name will never make it into the symbol table. This can make debugging difficult because it becomes the variable name does not exist so you won’t be able to search for it.
3. Use ‘const’ where ever possible: The const keyword is a very useful tool. Const allows the programmer to leverage the compiler to enforce rules with respect to the variable in question. Const can be used to define variables within the global or namespace scopes. It can also define objects as static within file, function and block scopes. When used with pointers, you can say whether the pointer is a const or whether the data pointed to is const (Both or neither). Inside functions it can be used for both static and non-static data members. I can here you asking “What does this mean?”
Well there are 4 pages of examples in the book, but what you really need to know is:
- Declaring something const can help compilers detect usage errors.
- Compilers enforce bit-wise const member functions, but you should program using logical const-ness.
- When const and non-const member functions have very similar implementations you can avoid code duplication by having the non-cont version call the const version.
4. Make sure that objects are initialized before they are used: In some languages defining a variable and not initializing it will result in it having a default value. This is not always the case with C++ and reading an uninitialized variable can result in the dreaded “undefined behaviour”. If you are defining an int variable, assign it an initial value.
int x = 6;
const char * text = “Hello”;
In the case of Objects, initialization is generally the responsibility of the constructor. The rules for C++ stipulate that data members of an Object must be initialized before the body of the constructor is entered. With objects you can use a member initialization list instead of assignments. The syntax for this would be :
AddressBookEntry:AddressBookEntry(const std:string& name, const std:string& address, const std:string& phoneNumber)
:theName(name), theAddress(address), thePhone(phoneNumber)
Many classes have multiple constructors and this list would have to be part of each constructor. In those cases, manually assigning variables can be less tedious.
The order of initialization can be tricky especially with non-local static objects. C++ will initialize base classes before derived classes and data members within a class are defined in the order they are declared.
Clear you mind for this one:
A problem can arise when the initialization of a non-local static object in one translation unit, uses a non-local static object in a different translation unit. In this case the later may not yet be initialized. I read the section on this, but I don’t think any explanation that I would put together at this moment would do it justice. I’ll try to circle back to this one.
To summarize this last guideline then:
- Manually initialize objects of built-in type, because C++ only sometimes initializes them.
- Use initialization lists in constructors to insure data members have been initialized before the code body is entered.
- Avoid initialization order problems with translation units by replacing non-local static objects with local static objects.
I think that’s enough for today. Stay tuned for more of the 55 guidelines in future posts.