为什么在此示例中需要后期绑定?
Why is late binding needed in this example?
我理解为什么在动态创建子类的对象时需要 virtual 关键字来覆盖,但是在以下示例中,为什么需要后期绑定(虚拟关键字(来覆盖?编译器在编译时无法判断 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
定义。如果你考虑一下,用一种语言编程真的很困难,该规则规定"如果你碰巧能够弄清楚pa
指向B
类型的对象,请使用B::f
,但如果你不确定pa
指向什么,请使用A::f
"。编译器应该多努力弄清楚pa
指向什么?如果将来编译器团队中的某个人想出了如何做出更准确的决定,那么程序的语义是否应该神奇地改变?你对此满意吗?
请注意,实际上,在您提供的两个代码片段中,编译器很有可能可以找出pa
指向的对象类型。因此,即使f
virtual
,优化编译器也可以省略代码以在vtable中查找正确的方法,而直接调用正确的方法。C++标准中没有任何内容禁止这种优化,我相信这是很常见的。因此,如果将来编译器团队中的某个人找到了确定变量类型的更好方法,它不会改变程序的语义 - 它仍然会调用相同的方法。但这可能会导致程序更快地调用您的方法。这种结果更有可能让你的未来快乐。
这一切都围绕着C++是一种静态类型语言的事实而解决。 因此,编译器将pa
视为指向A
的指针,而不是指向B
的指针(因为您将其声明为这样(,因此,如果您调用pa->f()
它将调用A::f()
而不是B::f()
,除非f()
被声明为虚拟。
事实上,这就是虚拟方法的重点 - 当您通过多态指针调用该方法时,调度到正确的方法。
在您展示的非常简单的示例中,是的,编译器可以简单地发现pa
是指向动态类型为B
的对象的指针。
但是,为了以您描述的方式使用该信息,必须满足以下几点:
- 在所有情况下,指针工作的规则都必须需要此信息。
- 这些信息必须不仅仅在一个简单的/人为的例子中提供(它不是 - 传递给另一个翻译单元中的函数
pa
,突然编译器运行处理该单元不知道动态类型是什么;当你之前问这个问题时,给出了一些例子,三个小时前( - 作为程序员,我们通常想要这种行为(我们不需要 - 如果我们愿意,我们可以选择加入
virtual
,但除此之外,我们想要漂亮的简单行为,其中表达式的类型定义了表达式的含义和操作数的作用(。
最后,请注意,您将动态与自动存储持续时间/分配方法描述为"运行时"与"编译时"并不十分准确,尽管考虑到构建和运行C++程序涉及多少个不同的抽象层,这些术语无论如何都会变得相当毛茸茸。
- 为什么 std::绑定错误参数可以成功?
- 为什么结构化绑定不使用"auto&"返回对结构成员的引用,而是返回成员本身
- 为什么 boost::comb 对结构化绑定的支持缺少结构化绑定机制对 boost::tuples::cons 的适应?
- 为什么结构化绑定不支持可变数组?
- 为什么定义复制构造函数会给我错误:无法将类型 'obj&' 的非常量左值引用绑定到类型为"obj"的右值?
- 为什么在此示例中需要后期绑定?
- 未定义的对象(〔basic.life〕/8):为什么允许引用重新绑定(和常量修改)
- 为什么右值不能绑定到非常量左值引用,除了写入临时无效的事实?
- 为什么"const auto [x, y]"绑定到引用类型时没有按预期运行?
- 为什么基于范围的 for 循环中的结构化绑定只是一个副本而不是引用?
- 为什么我不能将常量左值引用绑定到返回 T&&&的函数?
- 为什么我们需要 & in 绑定成员函数?
- 为什么 VS 无法将右值引用绑定到指针?
- 为什么 std::bind 绑定到成员函数时无法编译?
- 为什么此右值引用绑定到左值?
- 为什么我不能绑定?
- 为什么我不能在绑定中使用mem_fn函子?
- 为什么我可以在初始化引用后重新绑定引用?
- 为什么按引用传入会导致绑定引用类型错误
- 为什么结构化绑定取决于tuple_element