Да, в некоторых ситуациях добавление повторной реализации виртуальной функции изменит макет таблицы виртуальных функций. Это тот случай, если вы переопределяете виртуальную функцию из базового класса, который не является первым базовым классом (множественное наследование):
// V1
struct A { virtual void f(); };
struct B { virtual void g(); };
struct C : A, B { virtual void h(); }; //does not reimplement f or g;
// V2
struct C : A, B {
virtual void h();
virtual void g(); //added reimplementation of g()
};
Это меняет компоновку vtable таблицы C, добавляя запись для g()
(спасибо "Gof" за то, что я обратил на это мое внимание в первую очередь, как комментарий в http://marcmutz.wordpress.com/2010/07/25/bcsc-gotcha-reimplementing-a-virtual-function/).
Также, как упоминалось в другом месте, у вас возникает проблема, если класс, в котором вы переопределяете функцию, используется пользователями вашей библиотеки таким образом, что статический тип равен динамическому типу. Это может иметь место после того, как вы его обновили:
MyClass * c = new MyClass;
c->myVirtualFunction(); // not actually virtual at runtime
или создал его в стеке:
MyClass c;
c.myVirtualFunction(); // not actually virtual at runtime
Причиной этого является оптимизация, называемая «де-виртуализация». Если компилятор может доказать во время компиляции, что такое динамический тип объекта, он не будет генерировать косвенное обращение через таблицу виртуальных функций, а вместо этого вызовет правильную функцию напрямую.
Теперь, если пользователи скомпилированы со старой версией вашей библиотеки, компилятор вставит вызов для наиболее производной переопределения виртуального метода. Если в более новой версии вашей библиотеки вы переопределите эту виртуальную функцию в более производном классе, код, скомпилированный со старой библиотекой, по-прежнему будет вызывать старую функцию, тогда как новый код или код, в которых компилятор не может доказать динамический тип объект во время компиляции пройдет через таблицу виртуальных функций. Таким образом, данный экземпляр класса может столкнуться во время выполнения с вызовами функции базового класса, которые он не может перехватить, что может привести к нарушениям инвариантов класса.