为什么在此示例中需要后期绑定?
Why is late binding needed in this example?
我理解为什么动态创建子classes的对象时需要virtual关键字来覆盖,但是在下面的示例中,为什么需要后期绑定(virtual关键字)来覆盖?编译器不能在编译时告诉 pa 指向派生的 class 吗?
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
}
这一切都解决了 C++ 是一种静态类型语言这一事实。因此,pa
被编译器视为指向 A
的指针,而不是指向 B
的指针(因为您这样声明了它),如果您调用 pa->f()
它将调用 A::f()
而不是 B::f()
除非 f()
被声明为虚拟。
事实上,这就是虚方法的全部意义所在 - 当您通过多态指针调用方法时分派到正确的方法。
在您展示的非常简单的示例中,是的,编译器可以轻松发现 pa
是指向动态类型为 B
.
的对象的指针
但是,为了按照您描述的方式使用该信息,必须满足以下几点:
- 指针如何工作的规则必须需要此信息,在所有情况下
- 此信息必须不仅仅在 simple/contrived 示例中可用(它不是 — 将
pa
传递给另一个翻译单元中的函数,突然编译器 运行 处理那个单元不知道动态类型是什么;一些 examples 是你在三小时前问这个问题时给出的)
- 我们,作为程序员,通常会 想要 这种行为(我们不希望 - 如果我们愿意,我们可以选择加入
virtual
但否则我们想要漂亮简单表达式的类型定义表达式的含义和操作数的作用的行为)。
最后,请注意,您将动态 与 自动存储持续时间/分配方法描述为 "runtime" 与 "compile-time" 不太准确,尽管这些术语考虑到构建和 运行C++ 程序涉及多少不同的抽象层,无论如何都会变得相当混乱。
这个问题真的不围绕编译器是否可以"know"或不pa
所指对象的精确类型。它围绕 C++ 的语义展开。
当您声明方法 f
时,您是在告诉编译器您希望如何处理对 f
的调用。在 A::f
未声明 [=14=] 的情况下,您是说如果 pa->f()
被调用并且 pa
具有 A*
的声明类型,您需要编译器使用 A::f
中的定义。当然,*pa
是类型 A
的对象,即使它也可能是 A
.
的某些派生类型的对象
另一方面,如果您将 f
声明为 virtual
,您是在告诉编译器您希望它引用当前由 pa
,并使用来自该类型(或其适当的超类型)的 f
的定义。
C++ 的语义需要是确定性的。也就是说,作为程序员,您需要能够预测在任何给定情况下将使用 f
的哪个定义。如果您考虑一下,用一种具有规定 "use B::f
if you happen to be able to figure out that pa
points to an object of type B
, but use A::f
if you are not sure what pa
points to" 规则的语言进行编程真的很困难。编译器应该多努力去弄清楚 pa
指向什么?如果将来编译器团队中有人想出如何做出更准确的判断,你的程序的语义是否应该神奇地改变?你会满意吗?
请注意,在您提供的两个片段中,编译器 可以 实际上很有可能弄清楚 pa
指向的对象的类型是什么.所以即使 f
是 virtual
,优化编译器也可以省略在 vtable 中查找正确方法的代码,直接调用正确的方法。 C++ 标准中没有任何内容禁止这种优化,而且我相信这很常见。因此,如果将来编译器团队中的某个人想出一种更好的方法来确定变量的类型,它不会改变您的程序的语义——它仍然会调用相同的方法。但这可能会导致您的程序更快地调用您的方法。这是一个更有可能让你未来幸福的结果。
我理解为什么动态创建子classes的对象时需要virtual关键字来覆盖,但是在下面的示例中,为什么需要后期绑定(virtual关键字)来覆盖?编译器不能在编译时告诉 pa 指向派生的 class 吗?
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
}
这一切都解决了 C++ 是一种静态类型语言这一事实。因此,pa
被编译器视为指向 A
的指针,而不是指向 B
的指针(因为您这样声明了它),如果您调用 pa->f()
它将调用 A::f()
而不是 B::f()
除非 f()
被声明为虚拟。
事实上,这就是虚方法的全部意义所在 - 当您通过多态指针调用方法时分派到正确的方法。
在您展示的非常简单的示例中,是的,编译器可以轻松发现 pa
是指向动态类型为 B
.
但是,为了按照您描述的方式使用该信息,必须满足以下几点:
- 指针如何工作的规则必须需要此信息,在所有情况下
- 此信息必须不仅仅在 simple/contrived 示例中可用(它不是 — 将
pa
传递给另一个翻译单元中的函数,突然编译器 运行 处理那个单元不知道动态类型是什么;一些 examples 是你在三小时前问这个问题时给出的) - 我们,作为程序员,通常会 想要 这种行为(我们不希望 - 如果我们愿意,我们可以选择加入
virtual
但否则我们想要漂亮简单表达式的类型定义表达式的含义和操作数的作用的行为)。
最后,请注意,您将动态 与 自动存储持续时间/分配方法描述为 "runtime" 与 "compile-time" 不太准确,尽管这些术语考虑到构建和 运行C++ 程序涉及多少不同的抽象层,无论如何都会变得相当混乱。
这个问题真的不围绕编译器是否可以"know"或不pa
所指对象的精确类型。它围绕 C++ 的语义展开。
当您声明方法 f
时,您是在告诉编译器您希望如何处理对 f
的调用。在 A::f
未声明 [=14=] 的情况下,您是说如果 pa->f()
被调用并且 pa
具有 A*
的声明类型,您需要编译器使用 A::f
中的定义。当然,*pa
是类型 A
的对象,即使它也可能是 A
.
另一方面,如果您将 f
声明为 virtual
,您是在告诉编译器您希望它引用当前由 pa
,并使用来自该类型(或其适当的超类型)的 f
的定义。
C++ 的语义需要是确定性的。也就是说,作为程序员,您需要能够预测在任何给定情况下将使用 f
的哪个定义。如果您考虑一下,用一种具有规定 "use B::f
if you happen to be able to figure out that pa
points to an object of type B
, but use A::f
if you are not sure what pa
points to" 规则的语言进行编程真的很困难。编译器应该多努力去弄清楚 pa
指向什么?如果将来编译器团队中有人想出如何做出更准确的判断,你的程序的语义是否应该神奇地改变?你会满意吗?
请注意,在您提供的两个片段中,编译器 可以 实际上很有可能弄清楚 pa
指向的对象的类型是什么.所以即使 f
是 virtual
,优化编译器也可以省略在 vtable 中查找正确方法的代码,直接调用正确的方法。 C++ 标准中没有任何内容禁止这种优化,而且我相信这很常见。因此,如果将来编译器团队中的某个人想出一种更好的方法来确定变量的类型,它不会改变您的程序的语义——它仍然会调用相同的方法。但这可能会导致您的程序更快地调用您的方法。这是一个更有可能让你未来幸福的结果。