(我从一些 cmets 的答案开始,但决定我应该写下我自己的答案。)
我在这里稍微重新排列了您的代码,以便更容易编译和查看输出:
#include <iostream>
#ifdef V
#define VIRTUAL virtual
#else
#define VIRTUAL /*nothing*/
#endif
class A {
public: VIRTUAL char f() { return 'A';}
};
class B : public A {
public: char f() { return 'B';}
};
int main() {
A* pa;
B b;
pa = &b;
std::cout << pa->f() << std::endl;
}
编译运行显示:
$ c++ t.cc && ./a.out
A
$ c++ -DV t.cc && ./a.out
B
这表明virtual 关键字改变了程序的行为。这实际上是语言标准要求的。我认为,您的问题最好改写为为什么标准是这样编写的(它有一个更有用的通用答案)而不是编译器可以优化我的代码(它有一个具体但无用的答案:是的,它可以,但仍然需要打印A,而不是B)。
语言定义并没有禁止编译器执行特殊的优化技巧。相反——尤其是在这种情况下,对于 C++——语言规范专门试图让编译器编写者更容易优化它。这最终给 C++ 程序员带来了更多负担。
如果 C++ 是一种不同的语言 ...
您所说的功能,即virtual 关键字,正是因此而存在的。语言可以有不同的定义(和其他一些语言):他们可以说编译器作者不得假设,给定一些有效的A* pa,@ 987654328@ 指向A 类型的一些实际实例。那么:
std::cout << pa->f() << std::endl;
总是要弄清楚:*pa 的真正底层类型是什么,因此我应该在这里调用什么函数 f?
在这种假设的(非 C++)语言中,1一个优化的编译器可以获取您的代码并构建它以直接调用B::f(),因为pa指向B 类型的实例。但是在同一种语言中,试图进行大量优化的编译器无法对 pa 的底层类型由编译时无法预测的东西确定的函数做出假设:
void f(A* pa) {
std::cout << pa->f() << std::endl;
}
int main(int argc, char **argv) {
A a;
B b;
f(argc > 1 ? &b : &a);
}
这个程序在没有额外参数的情况下需要打印A,在使用额外的参数调用时需要打印B。因此,如果我们的非 C++ 语言缺少 virtual 关键字,或者将其定义为无操作,则函数 f——它在运行时调用 A::f() 或 B::f()——必须总是弄清楚要调用哪个底层函数。
1也不是 C。取名 D。也许是 P,来自 BCPL 进程?
结论
因为 C++确实有 virtual 关键字,我们构建的在基类 A 中具有非虚拟 f() 的变体可以通过 假设优化 pa->f() 调用 pa->f() 调用 A::f()。因此,优化编译器可以直接将"A\n" 写入std::cout,而不是实际调用A::f()。无论 C++ 编译器是否优化,调用必须产生A而不是B。
插入了virtual 关键字的变体不得假定pa->f() 调用A::f()。如果它可以优化到足以看到pa->f() 调用B::f(),因此,在编译时,完全消除调用并让函数写入"B\n",那没关系!如果它不能优化那么多,那也没关系——至少,就语言规范而言。
作为程序员,您需要了解virtual 关键字,并在您希望编译器 被强制 选择基于实际运行时类的正确函数,无论编译器是否足够聪明以在编译时执行此操作。如果你想允许并强制编译器每次只使用基类函数,你可以省略virtual关键字。