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:
- always manually initialize non-member objects of built-in type, becauese C++ sometimes initializes them and sometimes not.
- 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)
- 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.
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.
- For data members of
constand references, since they can’t be assigned (item 5), initialization list must be used.
- 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.
- 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
- 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)
- local static object:
- objects declared static inside functions (it’s local to a function)
- non-local static object:
- global objects
- objects defined at namespace scope
- objects declared static inside classes
- objects declared static at file scope
- 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):
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):
P.S.: There’s limitation for non-
conststatic 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.