Сбой в конструкторе с использованием виртуального наследования и делегирования конструкторов

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 или я допустил ошибку в коде?

7

Решение

Быстрый анализ кода сборки, сгенерированного компилятором 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) база B3) база D1,

Согласно правилам языка, в этом случае конструктор B и конструктор D1 не предполагается строить свою виртуальную базу D, База D уже построен в этой точке самым производным объектом A, Это именно то, что контролируется этим скрытым логическим параметром. Однако когда B::B() вызывается из B::B(int)компилятор передает неверное значение параметра (это жестко 1), что вызвало B::B() неправильно предположить, что он создает наиболее производный объект. Это очередь делает B восстановить общую виртуальную базу D, Эта реконструкция отменяет результаты правильного строительства, уже сделанного A::A(), Позже это вызывает сбой.

6

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

Насколько я могу судить, ваш пример кода должен работать.

Однако в качестве отступления ваше делегирование конструктора может считаться плохой практикой. У вас должен быть один полностью определенный конструктор, которому делегируются все менее определенные конструкторы, а не наоборот. Например:

struct B : public D2
{
B() : B(0) { }

B(int val) { }

void m() const { }
};
0

По вопросам рекламы [email protected]