是否可以通过在基 class 中添加新的虚函数来破坏代码?

Is it possible to break code by adding a new virtual function in the base class?

是否可以通过简单地将新的虚函数添加到基 class 来改变观察到的程序行为?我的意思是必须对代码进行其他更改。

以下程序打印 OK。取消注释 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()后,D::foo()中对bar()的调用绑定从静态变为动态。

二进制不兼容。

如果您有一个外部可加载模块(即 DLL),那么它使用基的旧定义 class 您将遇到问题。或者,如果加载程序具有旧定义而 DLL 具有新定义,则问题相同。如果您出于某种原因使用原​​始二进制复制(不是任何类型的序列化)将对象保存在文件中,这也是一个问题。

这与虚函数的 C++ 规范无关,而是大多数编译器如何实现它们。

一般来说,如果 class 的 "interface" 发生变化(基数 class 或未发生变化),那么您应该重新编译使用 class 的所有内容。

当 API 以向后不兼容的方式更改时,不再保证依赖于 API 早期版本的代码可以正常工作。

所有派生 类 都依赖于其基础 类 的 API。

添加虚函数是向后不兼容的更改。 答案展示了 API 破损如何表现出来的一个很好的例子。

因此,是的,添加虚函数可能会破坏程序,除非依赖部分被修复以与新的 API 一起工作。这意味着无论何时添加虚函数,都应该检查所有派生的 类,并确保它们各自的 API 的含义没有被添加改变。

#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();
}

如果没有虚函数,基础 class b.foo() 是对 B::foo():

的非虚调用
$ g++ virt.cc
$ ./a.out

在基础 class 中使用虚拟调用 C::foo():

$ g++ virt.cc -DADD_TO_BASE
$ ./a.out
Aborted (core dumped)

由于二进制不兼容,您还可能会遇到令人讨厌的未定义行为,因为将虚函数添加到非多态基 class 会改变其大小和布局(需要重新编译所有其他使用它的翻译单元).

向已经多态的基础添加一个新的虚函数 class 改变 vtable 的布局,要么在末尾添加一个新条目,要么改变其他函数的位置,如果添加在中间(和即使添加在基本 vtable 的末尾,对于任何派生的 classes 来说,它也位于 vtable 的中间,这些派生 classes 添加了新的虚函数)。这意味着使用 vtable 的已编译代码可能最终会调用错误的函数,因为它使用了 vtable 中的错误槽。

二进制不兼容问题可以通过重新编译所有相关代码来修复,但是行为上的静默更改(如顶部示例)不能简单地通过重新编译来修复。