为啥在这个例子中需要后期绑定? [复制]
Posted
技术标签:
【中文标题】为啥在这个例子中需要后期绑定? [复制]【英文标题】:Why is late binding needed in this example? [duplicate]为什么在这个例子中需要后期绑定? [复制] 【发布时间】:2020-05-22 16:01:17 【问题描述】:我理解为什么在动态创建子类的对象时需要使用 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
【问题讨论】:
B b2;
不是在编译时创建的。
你需要了解虚拟调用的机制。 pa 的类型是 A * 而不是 B*,所以默认情况下它将调用 A 的 f 版本。使用 virtual 关键字,新信息以 vtable 的形式存储,其中解决了对实际函数的调用
考虑bool use_b; std::cin >> use_b; pa = use_b ? &b2 : new A;
。
@shanker861 好的,但是 vtable 是在编译时创建的,对吗?那为什么指向 b2 的 pa 没有调用正确的 f() ?
@George 您是否建议编译器不调用 b2 的 f(),因为通常 pa 可能指向动态创建的东西?
【参考方案1】:
这个问题实际上并不围绕编译器是否可以“知道”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
指向什么?如果将来编译器团队中的某个人想出如何做出更准确的判断,你的程序的语义是否应该神奇地改变?你会满意吗?
请注意,实际上很有可能在您提供的两个 sn-ps 中,编译器可以找出pa
指向的对象的类型是什么。所以即使f
是virtual
,优化编译器也可以省略在vtable 中查找正确方法的代码,直接调用正确的方法。 C++ 标准中没有任何内容禁止这种优化,我相信它很常见。因此,如果将来编译器团队中的某个人找到了一种更好的方法来确定变量的类型,它不会改变程序的语义——它仍然会调用相同的方法。但这可能会导致您的程序更快地调用您的方法。这种结果更有可能让你的未来变得快乐。
【讨论】:
非常感谢您的回答!!我希望我可以投票,但我没有足够的代表【参考方案2】:这一切都解决了 C++ 是一种静态类型语言这一事实。因此,pa
被编译器视为指向A
的指针,而不是指向B
的指针(因为您已将其声明为这样),因此如果您调用pa->f()
,它将调用@ 987654325@ 而不是 B::f()
除非 f()
被声明为虚拟。
事实上,这就是虚方法的全部意义所在——当您通过多态指针调用方法时分派到正确的方法。
【讨论】:
好的,但是 virtual 关键字的作用是告诉编译器将绑定延迟到运行时,对吗?为什么要延迟到运行时绑定? 因为编译器不知道pa
实际上是指向B
类型的对象。该信息在编译时丢失。调用 virtual
方法会经过一个额外的间接级别(vtable)来分派到正确的方法。
vtable不是在编译时创建的吗?
是的,但这不会改变pa
的类型。
如果 vtable 已经创建了,为什么不能在编译时使用?【参考方案3】:
在您展示的非常简单的示例中,是的,编译器可以轻松发现 pa
是指向动态类型为 B
的对象的指针。
但是,为了以您描述的方式使用该信息,有几件事必须是真实的:
-
指针如何工作的规则必须要求此信息,在所有情况下
此信息必须在不仅仅是一个简单/人为的示例中可用(它不是 - 将
pa
传递给另一个翻译单元中的函数,突然编译器运行处理该单元不知道是什么动态类型是;一些examples 是在三个小时前你之前问这个问题时给出的)
作为程序员,我们通常想要这种行为(我们不想要 - 如果我们愿意,我们可以选择使用 virtual
,但除此之外,我们想要简单的行为,其中表达式的类型定义表达式的含义和操作数的作用)。
最后,请注意,您将动态 vs 自动存储持续时间/分配方法描述为“运行时”与“编译时”并不十分准确,尽管考虑到有多少这些术语无论如何都变得相当模糊构建和运行 C++ 程序涉及不同的抽象层。
【讨论】:
非常感谢您的回答!!我现在明白我的想法出了什么问题。您能否扩展关于动态变量与自动变量的最后一点?以上是关于为啥在这个例子中需要后期绑定? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Map 在这个例子中有一个 return 语句? [复制]
基本反应问题。为啥在 React 文档的这个例子中需要 useEffect ?