struct D
{
virtual void m() const = 0;
};
struct D1 : public virtual D { };
struct D2 : public virtual D { };
struct B : public D2
{
B() { }
B(int val) : B() { }
void m() const { }
};
struct A : public B, public D1
{
A() : B(0) { }
};
int main()
{
A a;
return 0;
}
Я получаю сбой с компилятором MSVC 2013 с приведенным выше кодом. Он работает без сбоев при компиляции с GCC 4.7.2. Иерархия классов изображена ниже.
D
/ \
D1 D2
| |
\ B
\ /
A
Это ошибка в компиляторе MS или я допустил ошибку в коде?
Быстрый анализ кода сборки, сгенерированного компилятором MSVC ++ 2013, показывает, что делегированный вызов из B::B(int)
в B()
сделано неправильно. Это ошибка в компиляторе.
Конструкторы MSVC ++ имеют скрытый логический параметр, который сообщает конструктору, создает ли он наиболее производный объект (true
) или вложенный базовый подобъект (false
). В этом примере только A::A()
должен получить true
в этом скрытом параметре, в то время как все вызовы конструктора более низкого уровня должны получать false
, Однако когда B()
вызывается из B::B(int)
Компилятор безоговорочно проходит 1
(true
) как этот скрытый параметр. Это неверно
; Code for `B::B(int)`
...
00F05223 push 1 ; <- this is the problem
00F05225 mov ecx,dword ptr [this]
00F05228 call B::B (0F010F0h) ; <- call to `B::B()`
...
В правильно сгенерированном коде, когда компилятор выполняет делегированный вызов конструктора, он должен передавать значение параметра, полученное от вызывающей стороны, а не жестко закодированный 1
,
Порядок немедленных вызовов суб-конструктора из A::A()
в этом примере это выглядит следующим образом: 1) общая виртуальная база D
, 2) база B
3) база D1
,
Согласно правилам языка, в этом случае конструктор B
и конструктор D1
не предполагается строить свою виртуальную базу D
, База D
уже построен в этой точке самым производным объектом A
, Это именно то, что контролируется этим скрытым логическим параметром. Однако когда B::B()
вызывается из B::B(int)
компилятор передает неверное значение параметра (это жестко 1
), что вызвало B::B()
неправильно предположить, что он создает наиболее производный объект. Это очередь делает B
восстановить общую виртуальную базу D
, Эта реконструкция отменяет результаты правильного строительства, уже сделанного A::A()
, Позже это вызывает сбой.
Насколько я могу судить, ваш пример кода должен работать.
Однако в качестве отступления ваше делегирование конструктора может считаться плохой практикой. У вас должен быть один полностью определенный конструктор, которому делегируются все менее определенные конструкторы, а не наоборот. Например:
struct B : public D2
{
B() : B(0) { }
B(int val) { }
void m() const { }
};