Инициализация базовых классов при использовании виртуального наследования

Я получаю неожиданные ошибки при компиляции следующего кода с использованием Xcode 5.1 на OS X.
Apple LLVM версии 5.1 (clang-503.0.40) (на основе 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;
};

Сообщенные ошибки:

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();

Насколько я понимаю, конструктор для виртуального базового класса (GrandParent) не вызывается классом, который наследуется от него (Parent1 или Parent2). Вместо этого конструктор вызывается конструктором конкретного класса (Child).

Это правильно?

Если я предоставляю конструктор по умолчанию для GrandParent, он компилируется нормально. Однако, если я создаю дочерний объект:

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

и проверить это я вижу:

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

Таким образом, возраст GrandParent при использовании Parent1 является неправильным, но для Parent2 это правильно.

Я что-то не так понял? Или ошибка может быть ошибкой компилятора?

ОБНОВИТЬ

Если я обновлю ctor для Parent1 (и сделаю то же самое для Parent2), чтобы:

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

теперь он компилируется. Проверка значений показывает:

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

Это явно не правильно. Кроме того, измененный код компилируется в Windows с использованием VS 2013 Express, и значения при проверке верны.

1

Решение

Все определенные ctors, по умолчанию или нет, должны быть действительными.

Пока инициализация виртуальных баз пропускается во время выполнения во всех, кроме самого производного ctor, он должен быть действительным.

Цитата из окончательного черновика C ++ 14 (n3936):

12.6.2 Инициализация баз и членов [class.base.init]

7 The список_выражений или же приготовился-INIT-лист в мем-инициализатор используется для инициализации обозначенного подобъекта (или, в случае делегирующего конструктора, полного объекта класса) в соответствии с правилами инициализации 8.5 для прямой инициализации.
[пример опущен] Инициализация выполняется каждым мем-инициализатор представляет собой полное выражение. Любое выражение в мем-инициализатор оценивается как часть полного выражения, которое выполняет инициализацию. мем-инициализатор где мем-инициализатор-идентификатор обозначает, что виртуальный базовый класс игнорируется во время выполнения конструктора любого класса, который не является самым производным классом.
8 В не делегирующем конструкторе, если данный потенциально сконструированный подобъект не обозначен meminitializer-идентификатор (включая случай, когда нет мем-инициализатора-лист потому что конструктор не имеет т е р-инициализатор), затем

  • если объект является нестатическим членом данных, который имеет скобки или равно-инициализатор и либо
    • класс конструктора является объединением (9.5), и никакой другой вариантный член этого объединения не обозначается мем-инициализатор-идентификатор или же
    • класс конструктора не является объединением, и, если объект является членом анонимного объединения, никакой другой член этого объединения не назначается мем-инициализатор-идентификатор,
      объект инициализируется, как указано в 8.5;
  • в противном случае, если объект является анонимным объединением или вариантом члена (9.5), инициализация не выполняется;
  • в противном случае объект инициализируется по умолчанию (8.5).
[Примечание: абстрактный класс (10.4) никогда не является самым производным классом, поэтому его конструкторы никогда не инициализируют виртуальные базовые классы, поэтому соответствующие mem-инициализаторы могут быть опущены. —Конечная записка]

Я особенно рекомендую вашему вниманию последнюю записку, которую вы могли бы использовать в качестве оправдания.
Проблема в том, что примечания не являются нормативными, и это примечание категорически противоречит нормативному тексту, предшествующему ему.

Кажется, clang ++ — 3.5.0 принял примечание как евангелие, а g ++ — 4.9.0 — нет:
http://coliru.stacked-crooked.com/a/ded8d46cc29ac79f

1

Другие решения

Когда программа выполняет строку

 Parent1(int age) : m_age(age)

он пытается создать объект Parent1, где он является производным классом типа GrandParent. И, как видно из определения конструктора класса Parent1, он ничего не делает, но пытается создать объект GrandParent, вызывая его конструктор по умолчанию.

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

Однако, поскольку вы определили стандартный конструктор класса GranParent как закрытый, компилятор выдаст эту ошибку.

1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector