As a simple example,
class A { public: int a = 7; };would be equivalent to
class A { public: A() : a(7) {} };The real benefits of member initializers do not become apparent until a class has multiple constructors. For many data members, especially private ones, all constructors initialize a data member to a common value as in the next example:
class A { public: A(): a(7), b(5), hash_algorithm("MD5"), s("class A example") {} A(int a_val) : a(a_val), b(5), hash_algorithm("MD5"), s("Constructor run") {} A(int b_val) : a(7), b(b_val), hash_algorithm("MD5"), s("Constructor run") {} A(D d) : a(f(d)), b(g(d)), hash_algorithm("MD5"), s("Constructor run") {} int a, b; private: // Cryptographic hash to be applied to all A instances HashingFunction hash_algorithm; // String indicating state in object lifecycle std::string s; };Even in this simple example, the redundant code is already problematic if the constructor arguments for hash_algorithm are copied incorrectly in one of A’s constructors or if one of the lifecycle states was accidentally misspelled as "Constructor Run". These kinds of errors can easily result in subtle bugs. Such inconsistencies are readily avoided using member initializers.
class A { public: A(): a(7), b(5) {} A(int a_val) : a(a_val), b(5) {} A(int b_val) : a(7), b(b_val) {} A(D d) : a(f(d)), b(g(d)) {} int a, b; private: // Cryptographic hash to be applied to all A instances HashingFunction hash_algorithm("MD5"); // String indicating state in object lifecycle std::string s("Constructor run"); };Not only does this eliminate redundant code that must be manually synched, it makes much clearer the distinctions between the different constructors. (Indeed, in Java, where both forms of initialization are available, the use of member initializers is invariably preferred by experienced Java programmers in examples such as these.)
Now suppose that it is decided that MD5 hashes are not collision resistent enough and that SHA-1 hashes should be used. Without member initializers, all the constructors need to be updated. Unfortunately, if one developer is unaware of this change and creates a constructor that is defined in a different source file and continues to initialize the cryptographic algorithm to MD5, a very hard to detect bug will have been introduced. It seems better to keep the information in one place.
It may happen that a data member will usually have a particular value, but a few specialized constructors will need to be cognizant of that value. If a constructor initializes a particular member explicitly, the constructor initialization overrides the member initializations as shown below:
class A { public: A(): a(7), b(5) {} A(int a_val) : a(a_val), b(5) {} A(int b_val) : a(7), b(b_val) {} A(D d) : a(f(d)), b(g(d)) {} // Copy constructor A(const A& aa) : a(aa.a), b(aa.b), hash_algorithm(aa.hash_algorithm.getName()), s(aa.s) {} int a, b; private: // Cryptographic hash to be applied to all A instances HashingFunction hash_algorithm("MD5"); // String indicating state in object lifecycle std::string s("Constructor run"); };A few additional points are worth noting.
declarator constant-initializeroptto
declarator initializeropt
constant-initializer:
= constant-expression
A member-declarator can contain a constant-initializer only if it declares a static member (9.4) of const integral or const enumeration type, see 9.4.2.
If a static data member is of const integral or const enumeration type, its declaration in the class definition may specify a constant-initializer whose constant-expression shall be an integral constant expression (5.19).change
a constant-initializer whose constant-expression shall beto
an initializer whose initializer-clause or expression-list shall be
If a constructor has a mem-initializer for a non-static data member that has an initializer, the initialization specified by the mem-initializer shall be performed, and the non-static data member’s initializer shall be ignored.[ Example: given
struct A { int i = /* some integer expression with side effects */ ; A(int arg) : i(arg) { } // ... };the A(int) constructor will simply initialize i to the value of arg, and the side effects in i’s initializer will not take place. — end example ]
An implicitly declared copy constructor shall ignore any non-static data member’s initializer. [ Note: this implies that any side effect in such an initializer will not take place. See also the example in 12.6.2[5]. — end note ]