Требования по памяти составляют один указатель на каждый объект класса с виртуальными функциями, плюс одна vtbl для каждого такого класса.
На самом деле первое утверждение неверно, т.е. объект полученный в результате множественного наследования от полиморфных классов будет содержать несколько "унаследованных" указателей на vtbl.
Рассмотрим следующий пример. Пусть у нас есть полиморфный (т.е. содержащий виртуальные функции) класс B1: struct B1 { // я написал struct чтобы не возиться с правами доступа int a1; int b1;
virtual ~B1() { } };
И пусть имеющаяся у нас реализация размещает vptr (указатель на таблицу виртуальных функций класса) перед объявленными нами членами. Тогда данные объекта класса B1 будут расположены в памяти следующим образом: vptr_1 // указатель на vtbl класса B1 a1 // объявленные нами члены b1
Если теперь объявить аналогичный класс B2 и производный класс D
struct D: B1, B2 { virtual ~D() { } };
то его данные будут расположены следующим образом: vptr_d1 // указатель на vtbl класса D, для B1 здесь был vptr_1 a1 // унаследованные от B1 члены b1 vptr_d2 // указатель на vtbl класса D, для B2 здесь был vptr_2 a2 // унаследованные от B2 члены b2
Почему здесь два vptr? Потому, что была проведена оптимизация, иначе их было бы три.
Я, конечно, понял, что вы имели ввиду: "Почему не один"? Не один, потому что мы имеем возможность преобразовывать указатель на производный класс в указатель на любой из базовых классов. При этом, полученный указатель должен указывать на корректный объект базового класса. Т.е. если я напишу: D d; B2* ptr=&d;
то в нашем примере ptr укажет в точности на vptr_d2. А собственным vptr класса D будет являться vptr_d1. Значения этих указателей, вообще говоря, различны. Почему? Потому что у B1 и B2 в vtbl по одному и тому же индексу могут быть расположены разные виртуальные функции, а D должен иметь возможность их правильно заместить. Т.о. vtbl класса D состоит из нескольких частей: часть для B1, часть для B2 и часть для собственных нужд.