C++: Why prefer member initialization list over assignment?

The answer is simple – the latter could be a costly choice. I will try to explain this using a simple example. In the following program we have a class Bar that has a private data member of type Foo (another class). Now, lets say we want to create an object of type Bar initializing its data member with an object of type Foo, something like, Bar obj2(obj1); The following program defines the necessary functions to give an idea of what happens during the creation of such objects.

#include 

using namespace std;

class Foo {
public:
  Foo() { cout << "Foo: default ctor called." << endl; }
  Foo(const Foo &obj) { cout << "Foo: copy ctor called." << endl; }

  Foo & operator =(const Foo &obj)
  {
    cout << "Foo: overloaded assignment operator called." << endl;
    return *this;
  }

  ~Foo() { cout << "Foo: default dtor called." << endl; }
};

class Bar {
public:
  Bar() { cout << "Bar: default ctor called." << endl; }

  /* Parameterized constructor (without member initialization list) */
  Bar(Foo &foo)
  {
    cout << "Bar: ctor taking reference to Foo as param.." << endl;
    m_foo= foo;
  }

  ~Bar() { cout << "Bar: default dtor called." << endl; }

private:
  Foo m_foo;
};

int main()
{
  Foo obj1;
  Bar obj2(obj1);
  return 0;
}

Lets focus on Bar's parameterized constructor & how it affects the output.

..snip..

  /* Parameterized constructor (without member initialization list) */
  Bar(Foo &foo)
  {
    cout << "Bar: ctor taking reference to Foo as param.." << endl;
    m_foo= foo;                             /* Use assignment operator */
  }

..snip..

Output:
Foo: default ctor called.
Foo: default ctor called.
Bar: ctor taking reference to Foo as param..
Foo: overloaded assignment operator called.

Bar: default dtor called.
Foo: default dtor called.
Foo: default dtor called.

As we can see, while Bar's object is being constructed,

  1. Foo's default constructor is first invoked to construct the Foo's part of Bar, and then
  2. Foo's assignment operator is invoked to initialize it with the object supplied as parameter to this constructor.

Said that, let us now see how this can be improved by using member initialization list in Bar's parameterized constructor's definition.

..snip..

  /* Parameterized constructor (with member initialization list) */
  Bar(Foo &foo) : m_foo(foo)
  {
    cout << "Bar: ctor taking reference to Foo as param.." << endl;
  }

..snip..

Output:
Foo: default ctor called.
Foo: copy ctor called.
Bar: ctor taking reference to Foo as param..

Bar: default dtor called.
Foo: default dtor called.
Foo: default dtor called.

Looking at the above output, we can see that during the construction of Bar's object, Foo is being constructed & initialized directly by Foo's copy constructor, instead of using the default constructor to create an uninitialized object followed by the assignment operator.

Hence using member initialization list, one can completely avoid the use for overloaded assignment operator and thus save some precious CPU clocks.

Leave a Reply

Your email address will not be published. Required fields are marked *