A problem of order - constructor initialization
Written by Alex Armstrong   
Article Index
A problem of order - constructor initialization
Solution

Initializers can be used to make constructors more elegant but they also introduce subtle problems. This brainteaser for C++ programmers illustrates a common pitfall.

 

C++ is a language that has many sophisticated constructs but as it is built on C, a more primitive language, there are sometimes problems when the sophisticated meets the simple.

 

Banner

Background

Initializers can be used to make constructors more elegant. They really come into their own when you want to initialize instances of other classes and this is where the syntax originates. That is if you have a base class

class MyBase {...}

and you derive another class from it

class MyClass:MyBase
{
public:
  MyClass(int myval)
{
}
}

then you can call the base class's constructor using the syntax:

class MyClass:MyBase
{
public:
MyClass(int myval):MyBase(myval) {}
}

Where of course you pass what ever parameters are necessary into the call to the MyBase constructor.

It makes logical sense to extend the syntax to allow member objects to be initialized in the same way. For example, if we have another class:

class MyClass2
{
int x;
public:
MyClass2(int v)
{
x=v;
}
};

we can declare an instance of this as a member of MyClass and initialize it using the constructor:

class MyClass
{
private:
MyClass2 MyObj;
public:
MyClass(int value) : MyObj(value)
{
}
};

The call is to the constructor but notice that the instance name is used - that is MyObj(value) is a call to MyClass2(value).

What is perhaps a bit odd even if it is logical is the further extension of the syntax to allow the initialization of any variable even if it doesn't actually have a constructor. For example:

class MyClass
{
private:
int MyInt;
public:
MyClass(int value) : MyInt(value)
{
}
};

This is equivalent to:

class MyClass
{
private:
int MyInt;
public:
MyClass(int value)
{
MyInt=value;
}
};

As long as you can cope with the functional form of the initialization, which can also be used in place of simple assignment initialization e.g. you can write:

int i=1;

or

int i(1);

and i is initialized to one in both cases. It is worth adding that initializers can use expressions to set the value. For example:

int i(2*3);

is fine and sets i to six.

Puzzle

Now that you know everything you need to about constructor initialization lists it is time to see if you can solve the puzzle. As always the puzzle code presented here is reduced to the point where it might not be realistic but it encapsulates the problem. The real code was much more complex and so much harder to debug.

The programming team were very keen on enforcing code standard and all constructor initialization was done using initializer lists. For example:

class MyClass
{
private:
int b;
int a;
public:
MyClass(int value) : b(value*2),a(b)
{ ...
}
};

In the real code the initialization was more complex but this captures the essence - the first member was set to a computed value and the second was then set to the result.

This worked and worked for a long time. However another member of the team had a utility that tidied up declarations by collected them all together at the start of each block and then sorted them into alphabetical order.  You can argue for and against this "tidying" but it is clear that it shouldn't modify the meaning of the program that it is applied to.

Using it on the above example produces:

class MyClass
{
private:
int a;
int b;
public:
MyClass(int value) : b(value*2),a(b)
{ ...
}
};

This worked and the program compiled and ran but after a few tests it was clear the program was misbehaving. What is more the misbehaving wasn't deterministic or repeatable. It looked as if there was a race error or something sophisticated to track down.

What is going on?

Go to the next page to see the answer.

 <ASIN:0321543726>

<ASIN:0672329417>

<ASIN:0201379260>