«Средние классы» в графе наследования алмазов с использованием виртуального базового конструктора не по умолчанию: почему это не ошибка компиляции? - PullRequest
0 голосов
/ 04 января 2019

Рассмотрим граф наследования алмазов (т. Е. Виртуальный базовый класс).Из предыдущих вопросов мы знаем, что при построении наиболее производный класс напрямую вызывает конструктор по умолчанию (0-arg) (виртуальной) базы.

Но мы такжезнать из ответов на предыдущий вопрос (например, здесь ), что если «средние» классы в ромбах имеют конструкторы, которые используются самым производным классом, и эти конструкторы «вызывают» конструкторы не по умолчанию их(виртуальный) базовый класс (через список инициализации), то есть не соблюдается… хотя тела конструкторов «средних» классов выполнены.

Почемуя бы подумал, что это должна быть ошибка компиляции. (Обнаружено, конечно, когда самый производный класс объявлен и алмаз создан.)

Я ищу две вещи:

  • где в стандарте это указано?
  • происходит ли этот вид явно еще игнорируемого кода где-либо еще в языке?

Пример кодакоторогоЯ говорю о следующих фактических и ожидаемых результатах ниже:

B 0arg-ctor
Mf 0arg-ctor
Mt 0arg-ctor
useD

ожидаемый результат:

ERROR: (line 19) struct `D` creates a diamond inheritance graph where an explicitly
    written invocation of a virtual base class constructor is ignored (for base 
    classes `Mf`and `Mt` and ancestor virtual base class `B`

код:

#include <iostream>
using namespace std;

struct B {
    B() noexcept { cout << "B 0arg-ctor" << endl; };
    B(bool) noexcept { cout << "B 1arg-ctor" << endl; };
};

struct Mf : public virtual B
{
    Mf() : B(false) { cout << "Mf 0arg-ctor" << endl; }
};

struct Mt : public virtual B
{
    Mt() : B(true) { cout << "Mt 0arg-ctor" << endl; }
};

struct D : public Mf, public Mt { };

void useD(D) { cout << "useD" << endl; }

int main()
{
    D d;
    useD(d);
    return 0;
}

Ответы [ 4 ]

0 голосов
/ 04 января 2019

Добавление нового класса в кодовую базу не должно приводить к тому, что правильно сформированные классы внезапно становятся недействительными.Это было бы языковой катастрофой.Если Derived инициализирует свою виртуальную базу Base , и это правильный код, то существование класса, полученного в дальнейшем, не должно не влиять на достоверность Derived .Ваше ожидание почти полностью исключает наследование от любого класса просто потому, что случается использовать виртуальное наследование где-то и делает виртуальное наследование непригодным для использования.

Но для запрошенных вами ссылок (из черновика n4762):

10.9.2 / 13:

В конструкторе без делегирования инициализация выполняется в следующем порядке: - Сначала и только для конструктора самого производного класса (6.6.2), виртуальные базовые классы инициализируются в том порядке, в котором они появляются при обходе слева направо по глубине направленного ациклического графа базовых классов, где «слева направо» - порядок появления базовых классов.классы в списке базовых спецификаторов производного класса.

И вторая часть, о которой вы спрашивали, инициализатор виртуальной базы в классе, не являющемся производным, описан здесь, в 10.9.2 / 7.:

Mem-initializer, где mem-initializer-id обозначает виртуальный базовый класс , игнорируется во время выполненияконструктор любого класса, который не является самым производным классом.

0 голосов
/ 04 января 2019

Правила инициализации баз и членов указаны в [class.base.init] .

В частности, p7 :

A mem-initializer , где mem-initializer-id обозначает виртуальный базовый класс, игнорируется во время выполнения конструктора любого класса, который не является самым производным классом.

и его дополнение в p13 :

Сначала и только для конструктора самого производного класса ([intro.object]), виртуальная базаклассы инициализируются в том порядке, в котором они отображаются при обходе слева направо по глубине направленного ациклического графа базовых классов, где «слева направо» - порядок появления базовых классов в производном классе base-specier-list .

Следовательно, инициализаторы B(true) и B(false) игнорируются при инициализации Mf и Mt, поскольку они не являются наиболее производным классоми инициализация D приводит с thИнициализация B.Инициализатор для него не предусмотрен, поэтому используется B().

Сделать это не скомпилированным будет в принципе невозможно?Для начала рассмотрим:

struct Mf : public virtual B { };
struct D : public Mf { };

Это инициализирует B, но неявно.Вы хотите, чтобы это было ошибкой для Mf, поскольку его инициализация будет игнорироваться?Я предполагаю нет - в противном случае эта языковая функция была бы совершенно непригоднаА как насчет:

struct Mf : public virtual B { Mf() : B() { } };
struct D : public Mf { };

Это ошибка?Это в основном означает то же самое, хотя.Что, если у Mf есть члены, которые нужно инициализировать, и я, по привычке, точно так же, как перечисляю базовые классы?

struct Mf : public virtual B { Mf() : B(), i(42) { } int i; };
struct D : public Mf { };

Хорошо, вы говорите, вы допускаете ошибку, только если вы фактически предоставляете аргументы.Вот где возникает другое заблуждение:

Мы знаем из предыдущих вопросов, что при построении наиболее производный класс напрямую вызывает конструктор по умолчанию (0-arg) (виртуальной) базы.

Это не правда (и это не то, что утверждают эти ответы).Самый производный класс инициализирует виртуальные базы - но эта инициализация не должна быть по умолчанию.Мы могли бы написать:

struct D : public Mf, public Mt { D() : B(true) { } };

И действительно, нет никакого интересного различия между B() и B(true).Представьте, что конструктор был просто B(bool = true), тогда имеет ли значение, предоставит ли пользователь аргумент true?Было бы странно, если бы одна была ошибкой, а не другой, верно?

Если вы продолжите спускаться по этой кроличьей норе, я думаю, вы обнаружите, что допускать эту ошибку было бы либо слишком узким, либо чрезмерно ограничительным,

0 голосов
/ 04 января 2019

[class.mi] / 7 - Для объекта класса AA все виртуальные вхождения базового класса B в решетке классов AA соответствуют одному B подобъекту в объекте типа AA ...


[class.base.init] /7 - ... A mem-initializer , где mem-initializer-id обозначает виртуальный базовый класс, игнорируемый при выполнении конструктора любого класса, которыйне самый производный класс.


[intro.object] / 6 - Если завершенный объект, член данныхили элемент массива имеет тип класса, его тип считается наиболее производным классом , чтобы отличать его от типа класса любого подобъекта базового класса;объект наиболее производного класса типа или не относящегося к классу типа называется наиболее производным объектом .

Почему это так?

Помимо очевидного;поскольку стандарт так говорит, одно из возможных объяснений состоит в том, что, поскольку у вас есть только один подобъект базового класса, даже не имеет смысла разрешать middle основаниям взаимодействовать с инициализацией виртуальной базы.В противном случае, какой средний базовый класс вы бы ожидали инициализировать виртуальную базу, Mt или Mf?, Потому что для B(false) и B(true) означало бы два разных способа инициализации одного и того же объекта.

0 голосов
/ 04 января 2019

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...