This page looks best with JavaScript enabled

Item-4 Initialize objects before they're used

 ·  ☕ 5 min read · 👀... views

Since C++ is fickle about initialization, some good coding style is suggested.

There are basically only 3 rules we need to remember if wanting to avoid tragedy of using objects before they’re initialized:

  1. always manually initialize non-member objects of built-in type, becauese C++ sometimes initializes them and sometimes not.
  2. in constructors, prefer to use member initialization list to assignment inside the constructor body; data members in the initialization list is suggested to be in the same order as they are declared in the class (which helps to avoid reader confusion)
  3. replacing non-local static objects with local static objects in order to avoid initialization order problems across translation units.

1. Initialize non-member built-in type object

No need to remember rules about when built-in type object initialization is guaranteed to take place and when it isn’t, for they’re too complicated to know.

Just form a good habit to always initialize objects before using them manually.

1
2
3
4
int x = 0; // manual init. of an int
const char *text = "A C-style string"; // manual init. of a pointer
double d;
std::cin >> d;

2. Initialize data member with member initialization list

The arguments in the initialization list are used as constructor arguments for the various data members (will be copy-constructed), which will be more efficient than a call to the default constructor followed by a calll to the copy assignment operator.

  1. For data members of const and references, since they can’t be assigned (item 5), initialization list must be used.
  2. For built-in type data members, even though there’s no difference in cost betweeen initialization and assignment, it is a good habit to place them in member initialization list for consistency.
  3. For data members of user-defined type, since compilers will automatically call default constructors for absent ones in initialization list, even if you just want to call the default constructor, it is still a good habit to call the default constructors in the initialization list, just to make a good habbit to guarantee there’s no data member left uninitialzed.

Exception: multiple constructors share large common member initialization list may consider moving assignment to a single (private) function called by all the constructors, which will be helpful for initializing values from reading a file or database.

3. Initialze non-local static objects defined in different translation units

Glossary:

  1. static object: one that exists from the time it’s constructed until the end of the program (i.e., their destructors will be called when main finishes executing)
  2. local static object:
    • objects declared static inside functions (it’s local to a function)
  3. non-local static object:
    • global objects
    • objects defined at namespace scope
    • objects declared static inside classes
    • objects declared static at file scope
  4. translation unit: the source code giving rise to a single object file (basically a single source file plus all of its #include files)

Below is an example of non-local static objects requiring correct order of initialization (firstly tfs, secondly 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
//========================
// fileSystem.h       
class FileSystem {            // created by library developer
public:
    ...
    std::size_t numDisks() const;
    ...
};

extern FileSystem tfs;        // non-local static object for clients to use 
                              // "tfs" = "the file system"

//========================
// directory.h           
#include <fileSystem.h>       // created by library client
class Directory {
public:
    Directory( params );
    ...
};

Directory tempDir( params );  // non-local static object for
                              // directory of temporary files

//========================
// directory.cpp
Directory::Directory( params )
{
    ...
    std::size_t disks = tfs.numDisks(); // use the tfs objects
    ...
}

Since the relative order of initialization of non-local static objects (tfs vs. tempDir) defined in different translation units (fileSystem vs. directory) is undefined, and given the fact that C++ guarantees that local static objects are initialized when the object’s definition is first encountered during a call to that function, simply replace direct accesses to non-local static objects with calls to functions that return references to local static objects, and the problem is solved, with a little bonus of saving the cost of constructing/destructing the object in the situation of the function never being called (this design implementation is the well-known singleton pattern):

 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
//========================
// fileSystem.h       
class FileSystem {...};     // as before

FileSystem& tfs()           // replace the tfs object;
{                           // could also be static in the FileSystem class
    static FileSystem fs;   // define and initialize a local static object
    return fs;              // return a reference to it
} 

//========================
// directory.h           
#include <fileSystem.h>       
class Directory { ... };          // as before

Directory& tempDir( params )      // replace the tempDir object;
{                                 // could also be static in the Directory class
    static Directory td(params);  // definea and initialize a local static object
    return td;                    // return a reference to it
}

//========================
// directory.cpp
Directory::Directory( params )
{
    ...
    std::size_t disks = tfs().numDisks(); // use the new tfs()
    ...
}

P.S.: There’s limitation for non-const static object (local or non-local) in multiple threads scenarios. One way to deal with the trouble is to manually invoke all the reference-returning functions during the single-threaded startup portion of the program to eliminate initialization-related race condition.

Share on
Support the author with