Are there two types of member initializer lists in C++?

198 Views Asked by At

I saw two different ways to use member initializer lists. The first one is like that:

class ClassName {
   public:
      arg_type_1 varName1;
      arg_type_2 varName2;

      // Constructor.
      ClassName(arg_type_1 arg_name_1, arg_type_2 arg_name_2)
      : varName1(arg_name_1), varName2(arg_name_2) 
      {
      }
}

What is happening there is clear. In the constructor we have a list of arguments and we use them to initialize members of the class. For example arg_name_1 is used to initialize value for the varName1 variable of the class.

Another way to use the member initializer appears in the case of inheritance:

class ChildClass : public ParentClass
{
      ChildClass(string name) : ParentClass( name )
      {
           // What can we put here and why we might need it.
      }
};

What happens here is also clear. When we call the constructor of the ChildClass with one string argument, it calls the constructor of the ParentClass with the same string argument.

What is not clear to me, is how compiler distinguish these two cases (the syntax is the same). For example, in the second example, compiler might think that it needs to take value of the name variable and assign it to the ParentClass variable of the ChildClass and then it sees that such a variable is not declared in the ChildClass.

The second unclear point to me is why we might want to put some content in the body of the constructor from the second example. Even of there is nothing it already creates and returns an object using the constructor of the parent class.

4

There are 4 best solutions below

5
On BEST ANSWER

What is not clear to me, is how compiler distinguish these two cases (the syntax is the same).

Seeing an initializer in the list, the compiler first looks for member variables with that name. If it finds one, it tries to initialize that member with the given arguments. If it does not, it looks for a direct base class or virtual base class with that name, to initialize the base class subobject with the given arguments. These are the usual name lookup rules applied when you name something inside a class' method (including constructors).

In the rare and badly designed case when you have both a member variable and a direct base class with the same name, you will have to qualify the base class type, i.e. add the namespace:

struct B1 {
  B1(char const*) {};
};
namespace Foo {
  struct B2 {
    B2(bool) {};
  };
}

struct Weird : public B1, public Foo::B2 {
  int B1;
  double B2;

  Weird() 
    : ::B1("meow")
    , Foo::B2(false)
    , B1(42)
    , B2(3.14)
  {}
};

The second unclear point to me is why we might want to put some content in the body of the constructor from the second example. Even of there is nothing it already creates and returns an object using the constructor of the parent class.

It does not really return any object, it just creates that object. However, someone might want to call additional functions like this:

class ParentClass {
public:
  ParentClass(string name);
  void registerSomething(ParentClass const&);
}

class ChildClass : public ParentClass {
public:
  ChildClass(string name) : ParentClass( name ) {
    registerSomething(*this);
  }
};

There could be many reasons why someone might want to do this. In General, since the child class is "more" than the parent class, it is only natural if it wants to do more in its constructor than just initialize the base class subobject.

Some words on object lifetimes: Before entering the constructor body, you have only constructed the subobjects of the new object. The object itself begins its lifetime when execution leaves the constructor body. An analogy could be a car and its parts: Before enterign the car's constructor body, you have inflated the tyres, assembled the enginge and stamped out the parts of the body. But your car isnt a car if it has not been assembled, wich happens in the constructor body. This is mirrored in the destructor and can especially be seen in the presence of exceptions: If an exception occurs during an object's constructor, its destructor will not be called. Only the destructors of the subobjects will be called that have already been constructed completely. This is because if the constructor has not completed execution, the object never existed and there is nothing to call the destructor on.

3
On

What is not clear to me, is how compiler distinguish these two cases (the syntax is the same). For example, in the second example, compiler might think that it needs to take value of the name variable and assign it to the ParentClass variable of the ChildClass and then it sees that such a variable is not declared in the ChildClass.

Compiler knows that ParentClass is a type not a member in ChildClass. You won't be able to declare a member with name ParentClass

The second unclear point to me is why we might want to put some content in the body of the constructor from the second example. Even of there is nothing it already creates and returns an object using the constructor of the parent class.

This is for the cases where you want to use some specific non-default constructor in ParentClass.

3
On

ParentClass is a type and varName1 is a variable. They are two different kinds of entities that every compiler should distinguish.

There are lots of cases when you want put some code in subclass ctor. For example, you want to calculate the initial value of a subclass member based on the proper initialization of base class objects.

4
On

These are really the same for the compiler: the initializer list is used to initialize sub-objects. If the sub-object is a base class, it is named by its type; if it is a member, it is named by the member name; but the principle is the same in both cases.

The usual name lookup rules apply. If you give a member the same name as a base class, it will hide the base class, and you cannot specify the base class in the initializer list (which means that the base class must have a default constructor). (Don't do this. Establish a naming convention so that type names and variable names can never clash.)

As to why you might want code in the actual body of the constructor, there can be many reasons. Most often, it will be because of some post-processing you what to do on the initialized members. In other cases, it might be because you need to do some preprocessing before you can initialize the members. Or you might want to refactor some common processing out into a separate function.