简体   繁体   中英

Initialising base classes when using virtual inheritance

I'm getting unexpected errors when compiling the following code using Xcode 5.1 on OS X. Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)

class GrandParent
{
public:
    GrandParent(int age) : m_age(age)
    {
    }

    virtual ~GrandParent() {}

private:
    GrandParent();
    GrandParent(const GrandParent&);
    const GrandParent& operator=(const GrandParent&);

    int m_age;
};

class Parent1 : public virtual GrandParent
{
public:
    Parent1(int age) : m_age(age)
    {
    }

    virtual ~Parent1() {}

private:
    Parent1();
    Parent1(const Parent1&);
    const Parent1& operator=(const Parent1&);

    int m_age;
};

class Parent2 : public virtual GrandParent
{
public:
    Parent2(int age) : m_age(age)
    {
    }

    virtual ~Parent2() {}

private:
    Parent2();
    Parent2(const Parent2&);
    const Parent2& operator=(const Parent2&);

    int m_age;
};

class Child : public Parent1, public Parent2
{
public:
    Child(int grandParentAge, int parent1Age, int parent2Age, int childAge) :
        GrandParent(grandParentAge),
        Parent1(parent1Age),
        Parent2(parent2Age),
        m_age(childAge)
    {
    }

    virtual ~Child() {}

private:
    Child();
    Child(const Child&);
    const Child& operator=(const Child&);

    int m_age;
};

The errors reported are:

error: inherited virtual base class 'GrandParent' has private default constructor
    Parent1(int age) : m_age(age)
    ^
note: declared private here
    GrandParent();
    ^
error: inherited virtual base class 'GrandParent' has private default constructor
    Parent2(int age) : m_age(age)
    ^
note: declared private here
    GrandParent();

My understanding is that the constructor for the virtual base class (GrandParent) is not called by the class that inherits from it (Parent1 or Parent2). Instead, the constructor is called by the constructor of the concrete class (Child).

Is this correct?

If I supply a default constructor for GrandParent it compiles OK. However, if I construct a child object:

Child child(80, 50, 49, 20);

and inspect it I can see:

Child) child = {
  Parent1 = {
    GrandParent = (m_age = 49)
    m_age = 50
  }
  Parent2 = {
    GrandParent = (m_age = 80)
    m_age = 49
  }
  m_age = 20

So the age of GrandParent when using Parent1 is incorrect but for Parent2 it is correct.

Have I misunderstood something? Or could the error be a compiler bug?

UPDATE

If I update the ctor of Parent1 (and do the same for Parent2)to:

Parent1(int age) : GrandParent(100), m_age(age) { }

it now compiles. Inspecting the values reveals:

(Child) child = {
  Parent1 = {
    GrandParent = (m_age = 49)
    m_age = 50
  }
  Parent2 = {
    GrandParent = (m_age = 80)
    m_age = 49
  }
  m_age = 20

This is clearly not right. Furthermore, the modified code compiles on Windows using VS 2013 Express and the values on inspection are correct.

When the program executes the line

 Parent1(int age) : m_age(age) 

it tries to create a Parent1 object where it is a derived class of type GrandParent. And as can be seen from the constructor definition of the Parent1 class, it does nothing but tries to create a GrandParent object by calling the default constructor of it.

Parent1(int age) : m_age(age)   // Here the default constructor of GrandParent is called implicitly.
{
}

However since you have defined the default contructor of GranParent class as private, the compıler gives that error.

All defined ctors, defaulted or not, must be valid.

While the initialization of virtual bases is skipped at runtime in all but the most-derived ctor, it must still be valid.

Quote from C++14 final draft (n3936):

12.6.2 Initializing bases and members [class.base.init]

7 The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of 8.5 for direct-initialization.
[ example omitted ]
The initialization performed by each mem-initializer constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. A mem-initializer where the mem-initializer-id denotes a virtual base class is ignored during execution of a constructor of any class that is not the most derived class.
8 In a non-delegating constructor, if a given potentially constructed subobject is not designated by a meminitializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer ), then

  • if the entity is a non-static data member that has a brace-or-equal-initializer and either
    • the constructor's class is a union (9.5), and no other variant member of that union is designated by a mem-initializer-id or
    • the constructor's class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id , the entity is initialized as specified in 8.5;
  • otherwise, if the entity is an anonymous union or a variant member (9.5), no initialization is performed;
  • otherwise, the entity is default-initialized (8.5) .

[ Note: An abstract class (10.4) is never a most derived class, thus its constructors never initialize virtual base classes, therefore the corresponding mem-initializers may be omitted. —end note ]

I especially commend the last note to your attention, which you might have used as justification.
Trouble is, notes are non-normative, and this note is flatly contradicted by the normative text preceding it.

Seems clang++-3.5.0 took the note as gospel, while g++-4.9.0 did not:
http://coliru.stacked-crooked.com/a/ded8d46cc29ac79f

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM