Effective C++ item 4: Make sure that objects are initialized before they're used.

  1. For most types, a single call to a copy constructor is more efficient - sometimes much more efficient - than a call to the default constructor followed by a call to the copy assignment operator.
  2. Base classes are initialized before derived classes, and within a class, data members are initialized in the order in which they are declared.Once you’ve taken care of explicitly initializing non-member objects of built-in types and you’ve ensured that your constructors initialize their base classes and data members using the member initialization list, there’s only one more thing to worry about. That’s the order of initialization of non-local static objects defined in different translation units.Let’s first talk about static objects. A static object is one that exists from the time it’s constructed until the end of the program. Stack and heap-based objects are thus excluded. Included are global objects, objects defined at namespace scope, objects declared static inside classes, objects declared static inside functions, and objects declared static at file scope. Static objects inside functions are known as local static objects (because they are local to a function), and the other kinds of static objects are known as non-local static objects. Static objects are destroyed when the program exits, i.e., their destructors are called when main finishes executing. A translation unit is the source code giving rise to a single object file. It’s basically a single source file, plus all of its include files.
  3. Now let’s discuss a real problem about initialization of non-local static objects. Imagine at least two separately compiled source files, each of which contains at least one non-local static object (i.e., an object that’s global, at namespace scope, or static in a class or at file scope). If initialization of a non-local static object in one translation unit uses a non-local static object in a different translation unit, the object it uses could be uninitialized, because the relative order of initialization of non-local static objects defined in different translation units is undefined.

    Let’s see an example:
    In fileSystem.h file, you defined

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class FileSystem
    {
    public:
    ...
    std::size_t numDisks() const;
    ...
    };

    And in `a.cpp` file, you have:
    ```c++
    extern FileSystem theFileSystem; // definition is in some .cpp file in your library

    Then suppose somebody use your code like this in b.h and b.cpp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    ////////////////////////////////////////////////
    ////////////// In b.h file /////////////////////
    ////////////////////////////////////////////////
    class Directory
    {
    public:
    ...
    Directory();
    ...
    };
    ////////////////////////////////////////////////
    ///////////// In b.cpp file ////////////////////
    ////////////////////////////////////////////////
    Directory::Directory()
    {
    ...
    std::size_t disks = theFileSystem.numDisks();
    }

    If you’re going to define an object:

    1
    Directory tempDir;

    Unless theFileSystem is initialized before tempDir, tempDir‘s constructor will attempt to use theFileSystem before it’s been initialized.

    One solution to this is to make a small design change. All that has to be done is to move each non-local static object into its own function, where it’s declared static. These functions return references to the objects they contain. Then call the functions instead of referring to the objects. In other words, non-local static objects are replaced with local static objects. C++ guarantee that local static objects are initialized when the object’s definition is first encountered during a call to that function. Here’s the technique applied to both theFileSystem and tempDir:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    ////////////////////////////////////////////////
    //////////// In fileSystem.h file //////////////
    ////////////////////////////////////////////////
    class FileSystem
    {
    public:
    ...
    std::size_t numDisks() const;
    static FileSystem& theFileSystem();
    ...
    };
    ////////////////////////////////////////////////
    /////////// In fileSystem.cpp file /////////////
    ////////////////////////////////////////////////
    FileSystem& theFileSystem() // this replaces the theFileSystem object
    {
    static FileSystem fileSystem; // define and initialize a local static object
    return fileSystem;
    }
    ////////////////////////////////////////////////
    /////////////// In b.h file ////////////////////
    ////////////////////////////////////////////////
    class Directory
    {
    public:
    ...
    Directory();
    static Directory& tempDir();
    ...
    };
    ////////////////////////////////////////////////
    ///////////// In b.cpp file ////////////////////
    ////////////////////////////////////////////////
    Directory::Directory()
    {
    ...
    std::size_t disks = FileSystem::theFileSystem().numDisks();
    ...
    }
    Directory& tempDir() // this replaces the tempDir object
    {
    static Directory temp; // define and initialize a local static object
    return temp;
    }

    The reference-returning functions dictated by this scheme are always simple: define and initialize a local static object on line 1, return it on line 2. This simplicity makes them excellent candidates for inlining, especially if they’re called frequently.

Reference:
“Effective C++” Third Edition by Scott Meyers.