C++ W. Kandinsky, Equilibrium


Effecient C++

  1. Prefer the compiler to the preprocessor

  2. Instead of using a preprocessor macro, define a constant:
    const double ASPECT_RATIO = 1.653;
    const char * const authorName = "Scott Meyers";
    To limit the scope of a constant to a class you must make it a member, and to ensure there's at most one copy of the constant, you must make it a static member :
    class GamePlayer {
    static const int NUM_TURNS = 5; // constant declaration
    int scores[NUM_TURNS]; // use of constant
    You must still define static class members in an implementation file :
    const int GamePlayer::NUM_TURNS; // mandatory definition
    Older compilers may not accept this syntax, you can put the initial value at the point of definition :
    class EngineeringConstants { // this goes in the class header file
    static const double FUDGE_FACTOR;
    // this goes in the class implementation file
    const double EngineeringConstants::FUDGE_FACTOR = 1.35;
    The accepted way to compensate for compilers that (incorrectly) forbid the in-class specification of initial values for integral class constants is to use what is affectionately known as "the enum hack."
    class GamePlayer {
    enum { NUM_TURNS = 5 }; // "the enum hack" makes
    // NUM_TURNS a symbolic name for 5
    int scores[NUM_TURNS]; // fine
    Instead of function-like macros write real functions :
    template<class T>
    inline const T& max(const T& a, const T& b)
    { return a > b ? a : b; }

  3. Prefer <iostream> to <stdio.h>

  4. Prefer new and delete to malloc and free

  5. Because malloc and free are ignorant of constructors and destructors and that mixing malloc / free with new / delete can be VERY dangerous, you should stick with new / delete only.

  6. Prefer C++-style comments.

  7. C-style comments have problems with embedded comments.

  8. Use the same form in corresponding uses of new and delete.

  9. When you use new, two things happen. First, memory is allocated (via the function operator new).Second, one or more constructors are called for that memory. When you use delete, two other things happen: one or more destructors are called for the memory, then the memory is deallocated (via the function operator delete). The big question for delete is this: how many objects reside in the memory being deleted? The answer to that determines how many destructors must be called.Actually, the question is simpler: does the pointer being deleted point to a single object or to an array of objects? The only way for delete to know is for you to tell it. If you don't use brackets in your use of delete, delete assumes a single object is pointed to. Otherwise, it assumes that an array is pointed to:
    string *stringPtr1 = new string;
    string *stringPtr2 = new string[100];
    delete stringPtr1; // delete an object
    delete [] stringPtr2; // delete an array of objects

    Consider this typedef :
    typedef string AddressLines[4]; // a person's address
    // has 4 lines, each of which is a string
    string *pal = new AddressLines; // note that "new AddressLines returns a string*, just like new string[4] would

    This use of new must be matched with the array form of delete :

    delete pal; // undefined behavior!!!!
    delete [] pal; // fine
  10. Use delete on pointer members in destructors.

  11. Adding a pointer member almost always requires each of the following:
    • Initialization of the pointer in each of the constructors. If no memory is to be allocated to the pointer in a particular constructor, the pointer should be initialized to 0
    • Deletion of the existing memory and assignment of new memory in the assignment operator.
    • Deletion of the pointer in the destructor.
    Your class destructor usually shouldn't be using delete unless your class members were the ones who used new in the first place.
  12. Be prepared for out-memory conditions.