Я почти уверен, что проблему можно решить с помощью опции /vmg
.
В противном случае компилятор оптимизирует представление указателей на элементы в зависимости от определения класса.Классу с несколькими базовыми классами нужны разные указатели-члены, чем классу без них, а классу с виртуальными базовыми классами могут потребоваться даже более сложные.
Без /vmg
компилятор будет генерировать другой код в зависимости от того, имеет ли онвидел полное определение IStatesManager
, которое по имени я предполагаю как интерфейс с виртуальными методами.
Все модули, использующие этот класс, должны быть также скомпилированы с параметром / vmg, поэтому правильноеуказатель типа члена передается внутрь.
В качестве альтернативы, вы, возможно, могли бы включить заголовок для IStatemanager
в заголовок ControlNode
, но я предполагаю, что предварительное объявление было использовано намеренно для уменьшения зависимостей.
Редактировать: компилятор по-прежнему оптимизирует указатель метода, вызывающий код, когда он знает определение класса, и, таким образом, может исключить сложный случай виртуального вывода, как указано в комментариях, важное отличие заключается в инициализацииуказатели метода, который гарантировандля соответствия /vmg
.
Код, сгенерированный для этих функций, показывает разницу:
struct VirtMethods
{
virtual int m();
};
struct VDerived : public virtual VirtMethods
{
virtual int m() override;
};
int invokeit2(VirtMethods &o, int (VirtMethods::*method)());
int invokeit2(VDerived &o, int (VDerived::*method)());
int test(VirtMethods &o)
{
return invokeit2(o, &VirtMethods::m);
}
int test(VDerived &o)
{
return invokeit2(o, &VDerived::m);
}
Без /vmg
генерируется следующий код, который просто проходит простойуказатель на функцию в регистре для класса только с виртуальными методами.С другой стороны, класс с виртуальным базовым классом требует намного больше данных в структуре, передаваемой в память.
o$ = 8
int test(VirtMethods &) PROC ; test, COMDAT
lea rdx, OFFSET FLAT:[thunk]:VirtMethods::`vcall'{0,{flat}}' }' ; VirtMethods::`vcall'{0}'
jmp int invokeit2(VirtMethods &,int (__cdecl VirtMethods::*)(void)) ; invokeit2
int test(VirtMethods &) ENDP ; test
$T1 = 32
$T2 = 32
o$ = 64
int test(VDerived &) PROC ; test, COMDAT
$LN4:
sub rsp, 56 ; 00000038H
and DWORD PTR $T2[rsp+8], 0
lea rax, OFFSET FLAT:[thunk]:VDerived::`vcall'{0,{flat}}' }' ; VDerived::`vcall'{0}'
mov QWORD PTR $T2[rsp], rax
lea rdx, QWORD PTR $T1[rsp]
mov DWORD PTR $T2[rsp+12], 4
movaps xmm0, XMMWORD PTR $T2[rsp]
movdqa XMMWORD PTR $T1[rsp], xmm0
call int invokeit2(VDerived &,int (__cdecl VDerived::*)(void)) ; invokeit2
add rsp, 56 ; 00000038H
ret 0
int test(VDerived &) ENDP ; test
[thunk]:VDerived::`vcall'{0,{flat}}' }' PROC ; VDerived::`vcall'{0}', COMDAT
mov rax, QWORD PTR [rcx]
jmp QWORD PTR [rax]
[thunk]:VDerived::`vcall'{0,{flat}}' }' ENDP ; VDerived::`vcall'{0}'
[thunk]:VirtMethods::`vcall'{0,{flat}}' }' PROC ; VirtMethods::`vcall'{0}', COMDAT
mov rax, QWORD PTR [rcx]
jmp QWORD PTR [rax]
[thunk]:VirtMethods::`vcall'{0,{flat}}' }' ENDP
С другой стороны, с / vmg код для простого класса выглядит совершенно иначе:
$T1 = 32
$T2 = 64
o$ = 112
int test(VirtMethods &) PROC ; test, COMDAT
$LN4:
sub rsp, 104 ; 00000068H
lea rax, OFFSET FLAT:[thunk]:VirtMethods::`vcall'{0,{flat}}' }' ; VirtMethods::`vcall'{0}'
mov QWORD PTR $T1[rsp], rax
lea rdx, QWORD PTR $T2[rsp]
xor eax, eax
mov QWORD PTR $T1[rsp+8], rax
movups xmm0, XMMWORD PTR $T1[rsp]
mov DWORD PTR $T1[rsp+16], eax
movsd xmm1, QWORD PTR $T1[rsp+16]
movaps XMMWORD PTR $T2[rsp], xmm0
movsd QWORD PTR $T2[rsp+16], xmm1
call int invokeit2(VirtMethods &,int (__cdecl VirtMethods::*)(void)) ; invokeit2
add rsp, 104 ; 00000068H
ret 0
int test(VirtMethods &) ENDP ; test