This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-07-24


307. Initialization of a virtual base class subobject

Section: 11.9.5  [class.cdtor]     Status: NAD     Submitter: Andrei Iltchenko     Date: 31 Aug 2001

The second paragraph of section 11.9.5 [class.cdtor] contains the following text:

To explicitly or implicitly convert a pointer (an lvalue) referring to an object of class X to a pointer (reference) to a direct or indirect base class B of X, the construction of X and the construction of all of its direct or indirect bases that directly or indirectly derive from B shall have started and the destruction of these classes shall not have completed, otherwise the conversion results in undefined behavior.

Now suppose we have the following piece of code:

    struct  a  {
       a() :  m_a_data(0)  {   }
       a(const a& rhsa) :  m_a_data(rhsa.m_a_data)  {   }
       int   m_a_data;
    };

    struct  b :  virtual a  {
       b() :  m_b_data(0)  {   }
       b(const b& rhsb) :  a(rhsb),  m_b_data(rhsb.m_b_data)  {   }
       int   m_b_data;
    };

    struct  c :  b  {
       c() :  m_c_data(0)  {   }
       c(const c& rhsc) :  a(rhsc),// Undefined behaviour when constru-
                                   // cting an object of type 'c'
                           b(rhsc),  m_c_data(rhsc.m_c_data)  {   }
       int   m_c_data;
    };

    int  main()
    {   c   ac1,   ac2(ac1);   }

The problem with the above snippet is that when the value 'ac2' is being created and its construction gets started, c's copy constructor has first to initialize the virtual base class subobject 'a'. Which requires that the lvalue expression 'rhsc' be converted to the type of the parameter of a's copy constructor, which is 'const a&'. According to the wording quoted above, this can be done without undefined behaviour if and only if b's construction has already started, which is not possible since 'a', being a virtual base class, has to be initialized first by a constructor of the most derived object (11.9.3 [class.base.init]).

The issue could in some cases be alleviated when 'c' has a user-defined copy constuctor. The constructor could default-initialize its 'a' subobject and then initialize a's members as needed taking advantage of the latitude given in paragraph 2 of 11.9.3 [class.base.init].

But if 'c' ends up having the implicitly-defined copy constuctor, there's no way to evade undefined behaviour.

    struct  c :  b  {
       c() :  m_c_data(0)  {   }
       int   m_c_data;
    };

    int  main()
    {   c   ac1,   ac2(ac1);   }

Paragraph 8 of 11.4.5.3 [class.copy.ctor] states

The implicitly-defined copy constructor for class X performs a memberwise copy of its subobjects. The order of copying is the same as the order of initialization of bases and members in a user-defined constructor (see 11.9.3 [class.base.init]). Each subobject is copied in the manner appropriate to its type:

Which effectively means that the implicitly-defined copy constructor for 'c' will have to initialize its 'a' base class subobject first and that must be done with a's copy constructor, which will always require a conversion of an lvalue expression of type 'const c' to an lvalue of type 'const a&'. The situation would be the same if all the three classes shown had implicitly-defined copy constructors.

Suggested resolution:

Prepend to paragraph 2 of 11.9.5 [class.cdtor] the following:

Unless the conversion happens in a mem-initializer whose mem-initializer-id designates a virtual base class of X, to explicitly or implicitly convert ...

Notes from the 10/01 meeting:

There is no problem in this example. ac1 is fully initialized before it is used in the initialization of ac2.