是否可以通过简单地向基类添加新的虚函数来改变程序的观察行为?我的意思是不必对代码进行其他更改。
答案 0 :(得分:5)
以下程序打印确定。取消注释B
中的虚拟功能,它将开始打印 CRASH!。
#include <iostream>
struct B
{
//virtual void bar() {}
};
struct D : B
{
void foo() { bar(); }
void bar() { std::cout << "OK" << std::endl; }
};
struct DD : D
{
void bar() { std::cout << "CRASH!" << std::endl; }
};
int main()
{
DD d;
d.foo();
return 0;
}
问题在于,在引入虚拟函数B::bar()
之后,bar()
中对D::foo()
的调用的绑定从静态更改为动态。
答案 1 :(得分:5)
二元不兼容。
如果您有一个可外部加载的模块(即DLL),那么它将使用基类的旧定义,您将遇到问题。或者如果加载程序具有旧的定义并且DLL具有新的,那么它也是同样的问题。如果由于某种原因使用原始二进制复制(不是任何类型的序列化)将对象保存在文件中,这也是一个问题。
这与虚函数的C ++规范无关,而是大多数编译器如何实现它们。
一般来说,如果类的“接口”发生变化(基类与否),则应重新编译使用该类的所有内容。
答案 2 :(得分:2)
当以向后不兼容的方式更改API时,不再保证依赖于早期版本API的代码可以正常工作。
所有派生类都依赖于其基类的API。
添加虚拟功能是一种向后不兼容的变化。 Leon's答案显示了API破坏如何表现出来的一个很好的例子。
因此,添加虚函数可能会破坏程序,除非修复相关部分以使用新API。这意味着无论何时添加虚函数,都应检查所有派生类,并确保添加时未更改其各自API的含义。
答案 3 :(得分:2)
#include <stdlib.h>
struct A {
#if ADD_TO_BASE
virtual void foo() { }
#endif
};
struct B : A {
void foo() { }
};
struct C : B {
void foo() { abort(); }
};
int main() {
C c;
B& b = c;
b.foo();
}
如果没有虚函数,基类b.foo()
是对B::foo()
的非虚拟调用:
$ g++ virt.cc
$ ./a.out
对于基类中的虚拟,它是对C::foo()
的虚拟调用:
$ g++ virt.cc -DADD_TO_BASE
$ ./a.out
Aborted (core dumped)
由于二进制不兼容,您还可能会出现令人讨厌的未定义行为,因为向非多态基类添加虚函数会更改其大小和布局(需要重新编译使用它的所有其他翻译单元)。
向已经多态的基类添加新的虚函数会改变vtable的布局,或者在末尾添加新条目,或者如果在中间添加其他函数的位置,则更改其位置(即使最后添加的话也是如此)基本vtable,它位于vtable的中间,用于添加新虚函数的任何派生类)。这意味着使用vtable的已编译代码最终可能会调用错误的函数,因为它在vtable中使用了错误的插槽。
可以通过重新编译所有相关代码来修复二进制不兼容性,但不能通过重新编译来修复行为中的静默更改,例如顶部的示例。