C++ Initialization lists - I don't get it

37.3k Views Asked by At

In Effective C++, it is said that data elements in the initialization list need to be listed in the order of their declaration. It is further said that the reasoning for this is that destructors for data elements get called in the reverse order of their constructors.

But I just don't see how this could be a problem...

6

There are 6 best solutions below

1
On BEST ANSWER

Well consider the following:

class Class {
    Class( int var ) : var1( var ), var2(var1 ) {} // allright
    //Class( int var ) : var2( var ), var1(var2 ) {} // var1 will be left uninitialized

    int var1;
    int var2;
};

The second (commented out) constructor looks allright, but in fact only var2 will be initialized - var1 will be initialized first and it will be initialized with var2 that is not yet initialized at that point.

If you list initializers in the same order as member variables are listed in the class declaration risk of such errors becomes much lower.

0
On

For example if you have a class like this:

class X {
  int a,b;
  X(int i) : b(i),a(b) { } // Constructor
};

The constructor for class X looks like it initialises "b" first but it actually initialises in order of declaration. That means it will initialise "a" first. However "a" is initialised to the value of "b" which hasn't been initialised yet, so "a" will get a junk value.

0
On

The order of construction and destruction may be important when the members are also objects of some class that somehow depend on each other.

Consider a simple example:

class MyString {
public:
  size_t s_length;
  std::string s;
  MyString(const char *str) : s(str), s_length(s.length()) {}
};

The intention in this example is that member s_length holds the length of the stored string. This will not work however, because s_length will be initialised before s. So you call s.length before the constructor of s is executed!

0
On

It also could be a problem if one of the constructors of your members throws an exception. Then all members which were already properly constructed must be destructed in some order because there isn't something similar to initializer-lists for destructors. This order is the reverse order of appearance of the members in the class declaration. An example:

#include <iostream>

struct A1 {
  A1(int) { std::cout << "A1::A1(int)" << std::endl; }
  ~A1() { std::cout << "A1::~A1()" << std::endl; }
};

struct A2 {
  A2(int) { std::cout << "A2::A2(int)" << std::endl; }
  ~A2() { std::cout << "A2::~A2()" << std::endl; }
};

struct B {
  B(int) { std::cout << "B::B(int)" << std::endl; throw 1; }
  ~B() { std::cout << "B::~B()" << std::endl; }
};

struct C {
  C() : a1(1), a2(2), b(3) { std::cout << "C::C()" << std::endl; } // throw 1; }
  ~C() { std::cout << "C::~C()" << std::endl; }
  A1 a1;
  A2 a2;
  B b;
};

int main() {
  try {
    C c;
  } catch (int i) {
    std::cout << "Exception!\n";
  }
}

The output will be something like this:

A1::A1(int)
A2::A2(int)
B::B(int)
A2::~A2()
A1::~A1()
Exception!
0
On

Destruction is the reverse of construction, therefore elements are destructed in reverse order.

Let us say we have 2 members, a and b. b depends on a but a does not depend on b.

When we construct, we first construct a and now it exists we can construct b. When we destruct, if we destruct a first this will be a problem as b depends on it. But we destruct b first and integrity is ensured.

This is typical. For example in group theory, the inverse of fg is ~g~f (where ~f is the inverse of f)

When you dress, you first put on socks and then you put on shoes. When you undress you first remove the shoes, then the socks.

0
On

It is further said that the reasoning for this is that destructors for data elements get called in the reverse order of their constructors.

See Steve Jessop's comment at Class component order of initialisation