Topic: Defect Report: The initialization of a virtual base class subobject may have undefined behavior


Author: iltchenko@yahoo.com (Andrei Iltchenko)
Date: 31 Aug 2001 17:03:19 GMT
Raw View
[moderator note: forwarded to C++ committee -sdc]

Section: 12.7 - Construction and destruction [class.cdtor]
Submitter: Andrei Iltchenko (iltchenko@yahoo.com)

The second paragraph of section 12.7 [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 (12.6.2)
[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 lattitude given in paragraph 2 of section 12.6.2
[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 section 12.8 [class.copy] 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 12.6.2). Each subobject is copied in the manner appropriate to
its type:
   - if the subobject is of class type, the copy constructor for the
class is used;"

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 section 12.7 [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 ..."

Regards,

Andrei Iltchenko.



[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.research.att.com/~austern/csc/faq.html                ]