我理解为什么在动态创建子类的对象时需要用虚拟关键字来覆盖,但是,在下面的例子中,为什么需要后期绑定(虚拟关键字)来覆盖?编译器难道不能在编译时就知道pa指向的是一个派生类吗?
class A {
public: int f() { return 'A';}
};
class B : public A {
public: int f() { return 'B';}
};
int main() {
//I understand this case, since pa is pointing to
//something created at runtime, virtual keyword is needed
//to return 66
A* pa;
pa = new B;
cout << pa->f() << endl; //returns 65
//But why in this case where b2 is created during
//compile time, compiler doesn't know pa is pointing
//to a derived class?
B b2;
pa = &b2;
cout << pa->f() << endl; //returns 65
}
这个问题其实并不是围绕着编译器能不能 "知道 "对象的精确类型而展开的 pa
指的是。它围绕着C++的语义。
当你声明一个方法 f
,你告诉编译器你想如何调用到 f
要处理的。在这种情况下 A::f
未声明 virtual
你是说,如果 pa->f()
被称为和 pa
具有声明的类型为 A*
中的定义,你希望编译器使用在 A::f
. 当然可以 *pa
是一个类型的对象 A
的对象,尽管它也可能是某个派生类型的对象。A
.
另一方面,如果你宣布 f
将要 virtual
你告诉编译器,你希望它引用当前对象的最派生类型。pa
,并使用以下定义 f
从该类型(或其适当的超类型)。
C++的语义需要是确定性的。也就是说,作为程序员,你需要能够预测哪个定义的 f
在任何情况下都会被使用。如果你仔细想想,在一种语言中,如果有一条规则规定 "使用 B::f
如果你碰巧能够弄清楚,那么 pa
指向一个类型为 B
但使用 A::f
如果你不确定 pa
指向"。编译器要想弄清楚什么是 pa
指的是什么?如果将来,编译器团队中有人想出了如何做出更准确的判断,你的程序的语义是否应该神奇地改变?你会满意吗?
请注意,在你介绍的两个片段中,其实很有可能是编译器的 可以 指向的对象的类型是什么?pa
是。所以即使 f
是 virtual
一个优化的编译器可以省略在vtable中查找正确方法的代码,而直接调用正确的方法。C++标准中并没有禁止这样的优化,我相信这样的优化是很常见的。所以,如果将来编译器团队中有人想出了更好的确定变量类型的方法,它不会改变你的程序的语义--它仍然会调用相同的方法。但它可能会导致你的程序更快地调用你的方法。这是一个更有可能让未来的你满意的结果。
这一切都围绕着C++是一种静态类型的语言这一事实来解决。 因此。pa
被编译器视为指向一个 A
的指针,而不是指向一个 B
(因为您声明它是这样的),因此,如果您调用了 pa->f()
它将呼叫 A::f()
而非 B::f()
除非 f()
被声明为虚拟方法。
事实上,这就是虚拟方法的全部意义--当你通过多态指针调用方法时,派遣到正确的方法。
在你展示的这个简单的例子中,是的,编译器可以轻而易举的发现 pa
是指向一个对象的指针,其动态类型是 B
.
但是,为了以你所描述的方式使用这些信息,有几件事必须是真的。
pa
到另一个翻译单元中的函数,突然间编译器运行处理该单元时不知道动态类型是什么;一些 例子 在三小时前你提出这个问题时,你已经给出了答案)virtual
如果我们喜欢的话,但除此之外,我们想要简单的行为,表达式的类型定义了表达式的含义和操作数的作用)。)最后,请注意您对动态 与 自动存储持续时间分配方法作为 "运行时 "与 "编译时 "是不太准确的,不过考虑到构建和运行一个C++程序涉及到许多不同的抽象层,这些术语无论如何都会变得很毛躁。