2

Here is a simple program, that I am pretty sure has undefined behavior, but I want to be certain.

struct A {
  int x;
  A(int y):x(y) {}
  int foo() { return x; }
};

struct B : public A {
  B():A(foo()) {}
};

I am assuming that this has undefined behavior because the call to A::foo occurs before the A object is constructed and it reads a class variable that is not initialized.

However, if the implementation of A::foo is changed to { return 0; } rather than { return x; }, i.e. the function doesn't access the class members, is it still undefined behavior?

Further question - does anything change if A::foo is virtual?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Gregory
  • 1,205
  • 10
  • 17
  • 1
    Please post the actual code you are using. – Acorn May 26 '20 at 02:13
  • 1
    _"is it still undefined behavior?"_ yes – Language Lawyer May 26 '20 at 02:42
  • First case is equivalent to initializing a local variable `int x = foo(x);` and is undefined for the same reason. The second case (where `foo` doesn't access class members) is legitimate, see for example [1](https://stackoverflow.com/a/11174627), [2](https://stackoverflow.com/a/3899583). – dxiv May 26 '20 at 03:01
  • The standard guarantees that a base class will be fully initialized before the constructor of a derived class begins executing. – Mark Ransom May 26 '20 at 04:03
  • @dxiv it the citation from the standard in post 1 seems to suggest that all of the cases are undefined: "However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the result of the operation is undefined." – Gregory May 26 '20 at 04:10
  • @MarkRansom How is that compatible with the fact that the derived class constructor specifies the constructor call for the base class? Is the idea that the derived class's constructor doesn't "start" until after the base initializers have been executed? – Gregory May 26 '20 at 04:12
  • @Gregory And this part from the 2nd post says "***if*** *those functions attempt to use any sub-object of the object that has not been constructed, you are invoking UB*". I'll leave that to the real language lawyers to sort out. – dxiv May 26 '20 at 04:14

1 Answers1

3

This is undefined behavior in both cases (including virtual member functions), as specified in class.base.init:

Member functions (including virtual member functions, [class.virtual]) can be called for an object under construction... However, if these operations are performed in a ctor-initializer (or in a function called directly or indirectly from a ctor-initializer) before all the mem-initializers for base classes have completed, the program has undefined behavior.

This bullet point contains essentially the example code snippet that you have provided, and explicitly points out the undefined behavior.

cigien
  • 57,834
  • 11
  • 73
  • 112