Да, в некоторых случаях добавление новой реализации виртуальной функции будет изменять структуру таблицы виртуальных функций. Это тот случай, если вы повторно реализуете виртуальную функцию из базы, которая не является первым базовым классом (множественное наследование):
// 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
Причиной этого является оптимизация под названием «де-виртуализация». Если компилятор может доказать во время компиляции, каков динамический тип объекта, он не будет выдавать косвенность через таблицу виртуальных функций, а вместо этого вызовет правильную функцию напрямую.
Теперь, если пользователи скомпилировали старую версию вашей библиотеки, компилятор вставит вызов самой производной повторной реализации виртуального метода. Если в более новой версии вашей библиотеки вы переопределите эту виртуальную функцию в более производном классе, код, скомпилированный для старой библиотеки, по-прежнему будет вызывать старую функцию, тогда как новый код или код, в котором компилятор не смог подтвердить динамический тип объект во время компиляции будет проходить через таблицу виртуальных функций. Таким образом, данный экземпляр класса может столкнуться во время выполнения с вызовами функции базового класса, которые он не может перехватить, что может привести к нарушению инвариантов класса.
person
Marc Mutz - mmutz
schedule
21.04.2011