Этот ответ описывает проблему, возникающую во время выполнения, на примере GCC.Другие компиляторы будут генерировать другой код со схожей проблемой, поскольку в вашем коде заложена проблема отсутствия инициализации.
Без избежания динамического выделения памяти в целях эффективности, без универсального подхода, без шаблонов на каждом этапе.Разложенный, ваш код действительно сводится к:
class InterfaceA {};
class InterfaceB {};
class ObjectA : public virtual InterfaceA {
public:
ObjectA(InterfaceB *intrB) : m_intrB(intrB) {}
private:
InterfaceB *m_intrB;
};
class ObjectB : public virtual InterfaceB {
public:
ObjectB(InterfaceA *intrA) : m_intrA(intrA) {}
private:
InterfaceA *m_intrA;
};
#include <new>
void simple_init() {
void *ObjectA_mem = operator new(sizeof(ObjectA));
void *ObjectB_mem = operator new(sizeof(ObjectB));
ObjectA *premature_ObjectA = static_cast<ObjectA *>(ObjectA_mem); // still not constructed
ObjectB *premature_ObjectB = static_cast<ObjectB *>(ObjectB_mem);
InterfaceA *ia = premature_ObjectA; // derived-to-base conversion
InterfaceB *ib = premature_ObjectB;
new (ObjectA_mem) ObjectA(ib);
new (ObjectB_mem) ObjectB(ia);
}
Для максимальной читаемости скомпилированного кода давайте напишем это вместо глобальных переменных:
void *ObjectA_mem;
void *ObjectB_mem;
ObjectA *premature_ObjectA;
ObjectB *premature_ObjectB;
InterfaceA *ia;
InterfaceB *ib;
void simple_init() {
ObjectA_mem = operator new(sizeof(ObjectA));
ObjectB_mem = operator new(sizeof(ObjectB));
premature_ObjectA = static_cast<ObjectA *>(ObjectA_mem); // still not constructed
premature_ObjectB = static_cast<ObjectB *>(ObjectB_mem);
ia = premature_ObjectA; // derived-to-base conversion
ib = premature_ObjectB;
new (ObjectA_mem) ObjectA(ib);
new (ObjectB_mem) ObjectB(ia);
}
Что дает нам очень хорошийкод сборки .Мы можем видеть, что оператор:
ia = premature_ObjectA; // derived-to-base conversion
компилируется в:
movq premature_ObjectA(%rip), %rax
testq %rax, %rax
je .L6
movq premature_ObjectA(%rip), %rdx
movq premature_ObjectA(%rip), %rax
movq (%rax), %rax
subq $24, %rax
movq (%rax), %rax
addq %rdx, %rax
jmp .L7
.L6:
movl $0, %eax
.L7:
movq %rax, ia(%rip)
Сначала мы видим, что (неоптимизированный) код проверяет нулевой указатель, эквивалентный
if (premature_ObjectA == 0)
ia = 0;
else
// real stuff
Реальные вещи:
movq premature_ObjectA(%rip), %rdx
movq premature_ObjectA(%rip), %rax
movq (%rax), %rax
subq $24, %rax
movq (%rax), %rax
addq %rdx, %rax
movq %rax, ia(%rip)
Таким образом, значение, на которое указывает premature_ObjectA
, разыменовывается, интерпретируется как указатель, уменьшается на 24, результирующий указатель используется для чтениязначение, это значение добавляется к исходному указателю premature_ObjectA
.Поскольку содержимое premature_ObjectA
неинициализировано, это, очевидно, не может работать.
То, что происходит, - то, что компилятор выбирает vptr (указатель vtable), чтобы прочитать запись в -3 "quad" (3 * 8 =24) с уровня 0 (виртуальная таблица, подобная зданию, может иметь отрицательные этажи, это просто означает, что 0-й этаж не является самым нижним этажом):
vtable for ObjectA:
.quad 0
.quad 0
.quad typeinfo for ObjectA
vtable for ObjectB:
.quad 0
.quad 0
.quad typeinfo for ObjectB
vtable (каждого из этих объектов) начинается в конце , после «typeinfo для ObjectA», как мы видим в скомпилированном коде для ObjectA::ObjectA(InterfaceB*)
:
movl $vtable for ObjectA+24, %edx
...
movq %rdx, (%rax)
Так что во время построения vptr устанавливается на «floor»0 "виртуальной таблицы, которая находится перед первой виртуальной функцией, в конце, если виртуальной функции нет.
На 3 этаже находится начало виртуальной таблицы:
vtable for ObjectA:
.quad 0
Значение 0 для «InterfaceA
со смещением 0 внутри полного ObjectA
объекта».
Мелкие детали макета vtable будут зависеть от компилятора, принципы:
- инициализация скрытого элемента данных vptr (и, возможно,несколько других скрытых элементов) в конструкторе
- с использованием этих скрытых элементов во время преобразования в
InterfaceA
базовый класс
остается прежним.
Мое объяснениене дает исправления: мы даже не знаем, какая у вас проблема высокого уровня и почему вы используете эти аргументы конструктора и взаимозависимые классы.
Зная, что представляют эти классы,мы могли бы помочь больше.