According to the C
standard, calling a member function (in)directly of X
before all bases of X
are constructed results in undefined behaviour (draft n4910 §11.9.3.16 Initializing bases and members [class.base.init]). They provide following example:
class A {
public:
A(int);
};
class B : public A {
int j;
public:
int f();
B() : A(f()), // undefined behavior: calls member function but base A not yet initialized
j(f()) // well-defined: bases are all initialized
{}
};
What is the rationale behind this? I assume it results in undefined behavior in case f
would access a member of A
, because that member would not have been initialized yet. Are there other cases why this would result in undefined behaviour?
Edit:
I understand why in the given example the first call to f
is undefined behavior. However, I'm wondering what the rationale is for this. In other words: why is this defined as undefined behavior?
Assume that the definition of f
is as follows:
int B::f() {
return 0;
}
I would expect that this gets translated by most compilers to a function as follows:
int B::f(B *b) {
return 0;
}
This member function would never access any data member of B
. Hence, I wouldn't expect any undefined behaviour.
Now, consider f
has following definition:
int B::f() {
return this->j;
}
Which would get translated to something like this:
int B::f(B *b) {
return b->j;
}
This clearly accesses an uninitialized member of B
. Hence, undefined behaviour is expected.
To wrap it up: is the statement in the standard too general, or am I missing something and would both examples result in undefined behavior?
CodePudding user response:
One way to think about inheritance is that the derived class B
has all of the properties of the base class A
with some extra data and methods appended to the end. The data members of classes are constructed in the order of their declaration. So, when a program creates an instance of B
, it has to create all the members of A
first.
In Anoop Rana's answer, the quoted portion of the C standard says that "referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior." The problem with A(f())
in B
's initializer list is that the data member's of A
and B
have not been constructed and will be referenced in the call to f()
.
How do I know that the members of
B
have not been constructed yet?- Because the constructor to
A
is being called, which means that the members ofA
have not been constructed, and these constructions must finish before the construction ofB
's members can begin.
- Because the constructor to
How do I know that
B::f()
will reference data members ofB
and/orA
?- Because
B::f()
is not a static method. Notice that calling static methods is perfectly fine before a constructor runs because static methods can only reference static members of a class, and static members are created on program start, so they are already initialized.
- Because
CodePudding user response:
I think that the difference can be understood/explained using class.cdtor#1 which states:
For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.
(emphasis mine)
Now, we can apply this to the given example. In particular, in the first case A(f())
the execution of the base class' ctor has begun(and not the derived class') so referring to the non-static member function f
of derived class is undefined behavior. Note also that as f()
is the passed argument, the call to f
happen before the construction of A
has begun.
While in the second case j(f())
the execution of the derived class' ctor has begun and so referring to the non-static member function f
of the same derived class is valid now.
class A {
public:
A(int);
};
class B : public A {
int j;
public:
int f();
B() : A(f()), // execution of derived class' ctor has not started so calling f is UB
j(f()) // execution of derived class' ctor has begins so calling f is now valid
{}
};
In conclusion, calling a member function during the construction of the object is allowed but the problem with A(f())
was that the object under construction(whose ctor began executing) was A
's type but f
belong to the derived class B
.
While in the second case j(f())
this was not the case and hence this case was well-formed.