Ваш вопрос в основном сосредоточен на вызове обычных функций виртуальной базы, а не (гораздо) более интересного случая виртуальных функций виртуального базового класса (класса А в вашем пример) - но да, может быть стоимость. Конечно, все зависит от компилятора.
Когда компилятор скомпилировал A :: foo, он предположил, что «this» указывает на начало того, где члены данных для A находятся в памяти. В настоящее время компилятор может не знать, что класс A будет виртуальной базой любого другого класса. Но он с радостью генерирует код.
Теперь, когда компилятор скомпилирует B, изменений не будет, потому что, хотя A является виртуальным базовым классом, это все же единичное наследование, и в типичном случае компилятор разметит класс B, поместив данные класса A члены, за которыми сразу следуют члены данных класса B, так что B * может быть немедленно преобразован в A * без каких-либо изменений в значении, и, следовательно, никаких изменений не требуется. Компилятор может вызвать A :: foo, используя тот же указатель "this" (даже если он имеет тип B *), и это не причиняет вреда.
Та же самая ситуация для класса C - его все еще единственное наследование, и типичный компилятор поместит элементы данных A сразу после элементов данных C, так что C * может быть немедленно приведен к A * без каких-либо изменений в значении. Таким образом, компилятор может просто вызывать A :: foo с тем же указателем «this» (даже если он имеет тип C *), и в этом нет никакого вреда.
Однако ситуация совершенно иная для класса D. Макет класса D, как правило, будет состоять из элементов данных класса A, за которыми следуют элементы данных класса B, за которыми следуют элементы данных класса C, а затем элементы данных класса D.
Используя типичный макет, D * может быть немедленно преобразован в A *, поэтому штраф за A :: foo не налагается - компилятор может вызвать ту же самую процедуру, сгенерированную для A :: foo, без каких-либо изменений "это" и все в порядке.
Однако ситуация меняется, если компилятору необходимо вызвать функцию-член, такую как C :: other_member_func, даже если C :: other_member_func не является виртуальным. Причина в том, что когда компилятор написал код для C :: other_member_func, он предположил, что компоновка данных, на которую ссылается указатель «this», является элементами данных A, за которыми сразу же следуют члены данных C. Но это не так для экземпляра D. Компилятору может потребоваться переписать и создать (не виртуальный) D :: other_member_func, просто чтобы позаботиться о разнице в разметке памяти экземпляра класса.
Обратите внимание, что это другая, но похожая ситуация при использовании множественного наследования, но при множественном наследовании без виртуальных баз компилятор может позаботиться обо всем, просто добавив смещение или исправление к указателю "this", чтобы учесть, где Базовый класс «встроен» в экземпляр производного класса. Но с виртуальными базами иногда требуется переписать функцию. Все зависит от того, к каким элементам данных обращается вызываемая (даже не виртуальная) функция-член.
Например, если класс C определил не виртуальную функцию-член C :: some_member_func, компилятору может потребоваться написать:
- C :: some_member_func при вызове из фактического экземпляра C (а не D), как определено во время компиляции (поскольку some_member_func не является виртуальной функцией)
- C :: some_member_func, когда та же функция-член вызывается из фактического экземпляра класса D, определенного во время компиляции. (Технически это подпрограмма D :: some_member_func. Несмотря на то, что определение этой функции-члена неявно и идентично исходному коду C :: some_member_func, сгенерированный объектный код может немного отличаться.)
если код для C :: some_member_func использует переменные-члены, определенные как в классе A, так и в классе C.