Why did the C ++ creator choose to use a constructor initializer list to initialize base classes?

Why did the C ++ creator choose to use a constructor initializer list to initialize base classes? Why didn't he use syntax instead, like the second line of the comment in the following code?

class A{
public:
  A() { }
};

class B : A{
public:
  B() : A() { }   // Why decide to use this one, using constructor initializer, to initialize base class?
  B() { A(); }    // Why not choose this one? It easy for me to understand if it was possible.
};

int main(int argc, char *argv[]){
  /* do nothing */
}

      

+3


source to share


4 answers


The advantage of using an initializer list is that you have a fully initialized object in the body of the constructor.

class A {
public:
    A(const int val) : val(val) { }
private:
    const int val;
};

class B : A{
public:
  B() : A(123) {
      // here A and B are already initialized
  }
};

      

This way you have a guarantee that all members, even those of the parents, are not undefined.



Edit

In the example question, there is no need to call the base class in the initializer list, this is done automatically. So I modified the example a bit.

+5


source


I see several possible alternatives to the current C ++ rule that base classes and data members of a class type are initialized in the mem-initialiser list of the class type constructor (s). They all come with their own problems, which I will discuss below.

But first, note that you can't just write a derived constructor like this:

struct Derived : Base
{
  Derived()
  {
    Base(42);
  }
};

      

and expect the string to Base(42);

call the base class constructor. Throughout C ++, such a statement creates a temporary object of type Base

, initialized with 42

. Changing its value inside a constructor (or just inside its first line?) Would be a syntax nightmare.

This means that this will require a new syntax to be introduced. For the rest of this answer, I will use a hypothetical construct __super<Base>

to do this.

We will now discuss possible approaches that will be closer to your desired syntax and present their problems.

Option A

Base classes are initialized in the body of the constructor, while data members are still initialized in the mem-initialiser list. (This follows the letter of your question closest).

There would be an immediate problem with material being executed in a different order than written. For very good reasons, the rule in C ++ is that base class subobjects are initialized before any derived class data members. Imagine this use case:

struct Base
{
  int m;

  Base(int a) : m(a) {}
};

struct Derived
{
  int x;

  Derived() :
    x(42)
  {
    __super<Base>(x);
  }
};

      

Written this way, you can easily assume that it x

will be initialized first and then Base

initialized with 42

. However, this is not the case and the read x

will be undefined instead .

Option B



Mem-initialiser lists are removed entirely, base classes are initialized with __super

, and data members are simply assigned in the body of the constructor (much like Java does).

This may not work in C ++ because initialization and assignment are fundamentally different concepts. There are types in which two operations do significantly different things (like references) and types that cannot be assigned at all (like std::mutex

).

How would this approach deal with a situation like this?

struct Base
{
  int m;

  Base(int a) : { m = a; }
};

struct Derived : Base
{
  double &r;

  Derived(int x, double *pd)
  {
    __super<Base>(x);  // This one OK
    r = *pd;  // PROBLEM
  }
};

      

Consider a line with a caption // PROBLEM

. Either it means what it usually does in C ++ (in which case it assigns double

to "random place", references to uninitialized references r

), or do we change its semantics in constructors (or just in the initial parts of a constructor?) To initialize instead of assigning ... The former gives us a buggy program, while the latter introduces a completely chaotic syntax and unreadable code.

Option C

As above, but introduce special syntax to initialize the data item in the body of the constructor (as you did with __super

). Something like __init_mem

:

struct Base
{
  int m;

  Base(int a) : { __init_mem(m, a); }
};

struct Derived : Base
{
  double &r;

  Derived(int x, double *pd)
  {
    __super<Base>(x);
    __init_mem(r, *pd);
  }
};

      

Now, the question is, what have we achieved? We used to have a mem-initialiser list, a special syntax for initializing bases and members. The advantage was that it made it clear that this happens first, before the construction of the constructor body begins. We now have a special syntax for initializing bases and members, and we need to force the programmer to put it at the beginning of the constructor.


Note that Java can get away with a missing mem-initialiser list for several reasons that are not C ++ specific:

  • The syntax for creating an object is always new Type(args)

    in Java, whereas it Type(args)

    can be used in C ++ to construct objects by value.
  • Java only uses pointers where initialization and assignment are equivalent. For many types of C ++, the operations are different.
  • Java classes can only have one base class, so just enough super

    . C ++ will need to distinguish the base class you are talking about.
+1


source


B() : A() { }   

      

This will initialize the base class in a user-defined way.

B() { A(); }  

      

This will NOT initialize the base class in a specific way. This will create an object inside the ie constructorB(){}

0


source


I think the first initialization has better readability compared to the second, and you can define a class hierarchy as well.

-1


source







All Articles