Если у класса есть какая-либо виртуальная функция, объекты этого класса должны иметь vptr, то есть указатель на vtable, то есть виртуальную таблицу, из которой можно найти адрес правильной виртуальной функции. Вызываемая функция зависит от динамического типа объекта, который является наиболее производным классом, базовым подобъектом которого является объект.
Поскольку производный класс виртуально наследуется от базового класса, расположение базового класса относительно производного класса не является фиксированным, оно также зависит от динамического типа объекта. С gcc класс с виртуальными базовыми классами нуждается в vptr для поиска базовых классов (даже если виртуальной функции нет).
Кроме того, базовый класс содержит член данных, который расположен сразу после базового класса vptr. Схема памяти базового класса: { vptr, int
}
Если базовому классу требуется vptr, производному от него классу также потребуется vptr, но часто повторно используется «первый» vptr подобъекта базового класса (этот базовый класс с повторно используемым vptr называется первичной базой). Однако в данном случае это невозможно, потому что производному классу нужен vptr не только для определения того, как вызывать виртуальную функцию, но и для того, чтобы определить, где находится виртуальная база. Производный класс не может найти свой виртуальный базовый класс без использования vptr; если виртуальный базовый класс использовался как первичный, производный класс должен был найти первичный базовый класс, чтобы прочитать vptr, и должен был прочитать vptr, чтобы найти первичный базовый элемент.
Таким образом, у производного не может быть первичной базы, и он вводит свой собственный vptr.
Макет подобъекта базового класса типа derived
выглядит следующим образом: { vptr, int
} с vptr, указывающим на виртуальную таблицу для производных, содержащую не только адреса виртуальных функций , но и относительное расположение всех его виртуальных базовых классов (здесь просто base
), представленное как смещение.
Макет полного объекта типа derived
выглядит следующим образом: { подобъект базового класса типа derived
, base
}
Таким образом, минимальный возможный размер derived
составляет (2 int
+ 2 vptr) или 4 слова в обычных архитектурах ptr = int
= word, или 16 байт в данном случае. (А Visual C++ создает большие объекты (когда задействованы виртуальные базовые классы), я полагаю, что derived
будет иметь еще один указатель.)
Так что да, виртуальные функции имеют свою цену, и виртуальное наследование имеет свою цену. Затраты памяти на виртуальное наследование в этом случае составляют еще один указатель на объект.
В проектах со многими виртуальными базовыми классами затраты памяти на объект могут быть пропорциональны количеству виртуальных базовых классов или нет; нам нужно будет обсудить конкретные иерархии классов, чтобы оценить стоимость.
В проектах без множественного наследования или виртуальных базовых классов (или даже виртуальных функций) вам, возможно, придется эмулировать многие вещи, автоматически выполняемые компилятором для вас, с кучей указателей, возможно, указателей на функции, возможно, смещения... это может получить запутанно и подвержено ошибкам.
person
curiousguy
schedule
05.08.2012