Ошибка списка инициализации в gcc?
Рассмотрим следующий код, где B
- виртуальный базовый класс, унаследованный от D
до B1
и B2
:
#include <iostream>
class B
{
protected:
int x;
protected:
B(int x) : x{x}{std::cout << x << std::endl;}
};
class B1 : virtual public B
{
protected:
B1() : B(0){}
};
class B2 : virtual public B
{
protected:
B2() : B(10){}
};
class D : public B1, public B2
{
public:
D() : B(99), B1(), B2() {}
void print() {std::cout << "Final: " << x << std::endl;}
};
int main() {
D d;
d.print();
return 0;
}
См. рабочий пример здесь. Я использую выходы в конструкторе B
и после того, как D
был полностью сконструирован, чтобы отслеживать, что происходит. Все работает нормально, когда я скомпилировал приведенный выше пример с g++ - 4.8.1. Он печатает
99
Final: 99
потому что конструктор B
вызывается один раз из самого производного класса (D
), а также определяет окончательное значение x
.
Теперь появляется странная часть: если я меняю строку
D() : B(99), B1(), B2() {}
к новому равномерному синтаксису инициализации, то есть
D() : B{99}, B1{}, B2{} {}
странные вещи случаются. Во-первых, он больше не компилируется с ошибкой
prog.cpp: In constructor ‘D::D()’:
prog.cpp:17:5: error: ‘B1::B1()’ is protected
B1() : B(0){}
^
prog.cpp:31:27: error: within this context
D() : B{99}, B1{}, B2{} {}
(и то же самое для B2
, см. здесь), что не имеет смысла, потому что я использую его в производном классе, поэтому protected
должно быть в порядке. Если я исправлю это и сделаю конструкторы B1
и B2
общедоступными, а не защищенными, все будет полностью испорчено (см. здесь), так как выход становится
99
0
10
Final: 10
Итак, на самом деле части конструкторов B1
и B2
, которые инициализируют B
, все еще выполняются и даже изменяют значение x
. Это не должно иметь место для виртуального наследования. И помните, единственное, что я изменил, - это
- public вместо защищенных конструкторов в
B1
и B2
- используйте
classname{}
синтаксис в списке инициализации членов D
вместо classname()
.
Я не могу поверить, что такая базовая вещь идет не так, как в gcc. Но я протестировал его с помощью clang на моей локальной машине, и там все три случая скомпилируются и запускаются по назначению (т.е. Как первый пример выше). Если это не ошибка, может кто-то, пожалуйста, указать мне, чего я не вижу?
EDIT: мой первый поиск каким-то образом не вызвал его, но теперь я нашел этот другой вопрос, указав хотя бы защищенную/общедоступную ошибку. Однако это было gcc-4.7, поэтому я ожидал, что это будет рассмотрено в gcc-4.8. Итак, должен ли я заключить, что списки инициализаторов просто испорчены в gcc!?
Ответы
Ответ 1
Я не знаю, слишком ли поздно ответить на этот вопрос, но ваш код компилируется в GCC 4.9.2!
~$g++ -std=c++11 test.cpp
~$./a.out
99
Final: 99
~$gcc --version
gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Ответ 2
Что касается нескольких вызовов конструктора виртуального базового класса: я мог бы воспроизвести проблему со следующим кодом (с GCC 5.1.0).
#include <iostream>
struct V {
V(){std::cout << "V()\n";}
};
struct A : virtual V {
A() : V{} {std::cout << "A()\n";}
};
struct B : A {
B(): V{}, A{} {std::cout << "B()\n";}
};
int main(int argc, char **argv) {
B b{};
}
В результате получается следующий результат:
V()
V()
A()
B()
Я не думаю, что это верно, учитывая стандарт С++:
[class.base.init]/7
... Инициализация, выполняемая каждым mem-инициализатором, представляет собой полное выражение. Любое выражение в mem-инициализаторе оценивается как часть полного выражения, которое выполняет инициализацию. Мем-инициализатор где идентификатор mem-initializer обозначает виртуальный базовый класс, игнорируется во время выполнения конструктора любого класса, который не является самым производным классом.
Когда вызов конструктора A изменяется для использования скобок вместо скобок, результирующий исполняемый файл работает как ожидалось и только вызывает только V().
Я создал отчет об ошибке для GCC по этой проблеме: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70818
Изменить: я пропустил, что уже был отчет об ошибке: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55922