Почему использование объявленного наследующего конструктора НЕ инициализирует виртуальный базовый класс с помощью конструктора по умолчанию? - PullRequest
2 голосов
/ 22 апреля 2020

Я наткнулся на вопрос об объявленном с использованием наследующем конструкторе вчера. И после тщательного прочтения ответа, а также связанного стандартного черновика N3337 , я обнаружил, что могут быть некоторые несоответствия (или, по крайней мере, мое недопонимание), когда прямой базовый класс также использует using для наследования конструкторов от виртуальная база.

Вот пример:

struct A
{
    A(int, int){}
};

struct B : virtual A
{
    using A::A;
};

struct C : virtual A
{
    using A::A;
};

struct BC : B, C
{
    using B::B;
    using C::C;
}; 

// Now if we define an inline constructor that does the same
// as the constructor B inherited...
struct BB : virtual A
{
    BB(int a, int b):A(a,b){}
};

struct BBC : BB, C
{
    using BB::BB;
    using C::C;
};

int main()
{
    BC(1, 1);  // this compiles
    BBC(1, 1); // this doesn't because it needs to defaultly
               // initialize the virtual base A who doesn't
               // have a default constructor
}

Я понимаю, почему BBC не может скомпилироваться по точной причине, указанной в ответе выше, который я здесь повторю [class.inhctor] / 8

Неявно определенный наследующий конструктор выполняет набор инициализаций класса, который будет выполнен встроенным конструктором, написанным пользователем для этого класса, с помощью mem-initializer-list, чей единственный mem-initializer имеет идентификатор mem-initializer-id, который называет базовый класс, обозначенный в спецификаторе nested-name декларации using и в списке выражений, как указано ниже, и где составной оператор в его функции тело пусто ([class.base.init]).

и [class.base.init] / 10 * 102 2 *

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

Таким образом, виртуальный базовый класс должен быть создан по умолчанию, потому что его нет в mem-initializer-list наследующего конструктора BBC. Но A не имеет конструктора по умолчанию, поэтому он терпит неудачу (добавление A()=default;, очевидно, может заставить его скомпилироваться, но здесь дело не в этом).

Но мне пока не ясно, почему BC не делает нет этой проблемы? По сути это тот же пример, который дан cppreference в разделе Наследование конструкторов . Так что это должно работать. Но не выглядит ли это противоречащим стандарту? Когда B наследует конструкторы от A, он также получает ничего, кроме одного, отличного от значения по умолчанию, которое выполняет ту же инициализацию, что и определенная в BB, за исключением неявного. Тогда, когда этот конструктор из B наследуется от BC, разве не должно применяться то же правило, где A будет сконструировано по умолчанию, а значит, не скомпилировано?

Редактировать: @ j6t указал, что я смотрю на устаревший стандартный черновик. новый действительно лучше соответствует странице cppreference, которую я обнаружил ранее.

Одна вещь, которая остается для меня неясной, это то, что она объясняет, что должно произойти, если выбран виртуальный базовый конструктор, но как это наследуется внуком класса BC в первую очередь? Из того же draft кажется, что виртуальный базовый конструктор, введенный using, будет рассматриваться только в производном классе (B в данном случае). Как using в BC наследует конструктор, который на два уровня выше?

Я ожидал бы, что когда BC использует-объявляет конструкторы, то, что изначально унаследовано B от A, должно сначала обрабатываться как B конструкторы, а затем наследоваться BC , Но это не тот случай.

1 Ответ

1 голос
/ 22 апреля 2020

Я просто отвечаю на ваш вопрос в редакции.

... но как [виртуальный базовый конструктор] унаследован от внука класса B C в первую очередь?

Абзац , который вы цитировали, говорит:

Конструкторы, которые вводятся с помощью объявления-использования, обрабатываются так, как если бы они были конструкторами производного класса при поиске вверх по конструкторам производного класса ( class.qual ) [...].

т. е. он говорит, что эти конструкторы найдены путем поиска по квалифицированному имени. Это делает объявление-использование эффективно рекурсивной операцией: чтобы найти объект в базовом классе, он использует поиск по квалифицированному имени, а затем делает его доступным для поиска по квалифицированному имени.

Когда struct B использует using A::A, делает конструктор A::A доступным при поиске конструктора в struct B. Это происходит, когда struct BC использует using B::B; это квалифицированный поиск имени B в struct B; следовательно, он находит конструктор A::A и таким образом делает A::A доступным в struct BC.

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