当重写虚拟成员函数时,为什么重写函数总是变成虚拟的?
当我这样写时:
class A {
public: virtual void foo() = 0;
}
class B {
public: void foo() {}
}
...B::foo() 也变成虚拟的。这背后的理由是什么?我希望它的行为类似于 Java 中的 final
关键字。
添加:我知道它是这样工作的以及 vtable 是如何工作的:) 问题是,为什么 C++ 标准委员会没有留下直接调用 B::foo() 并避免 vtable 查找的空缺。
When I write like this:
class A {
public: virtual void foo() = 0;
}
class B {
public: void foo() {}
}
...B::foo() becomes virtual as well. What is the rationale behind this? I would expect it to behave like the final
keyword in Java.
Add: I know that works like this and how a vtable works :) The question is, why C++ standard committee did not leave an opening to call B::foo() directly and avoid a vtable lookup.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
该标准确实留下了直接调用 B::foo 的空缺,并避免了表查找:
输出:
因此您可以获得您所期望的行为,如下所示:
现在调用者应该使用 bar 而不是 foo。如果他们有 C*,那么他们可以将其转换为 A*,在这种情况下
bar
将调用C::foo
,或者他们可以将其转换为 B*,在这种情况下,bar
将调用B::foo
。如果需要的话,C 可以再次覆盖 bar,否则不必打扰,在这种情况下,在 C* 上调用bar()
会像您一样调用B::foo()
预计。但我不知道什么时候有人会想要这种行为。虚函数的全部要点是为给定对象调用相同的函数,无论您使用什么基类或派生类指针。因此,C++ 假设如果通过基类对特定成员函数的调用是虚拟的,那么通过派生类的调用也应该是虚拟的。
The standard does leave an opening to call B::foo directly and avoid a table lookup:
Output:
So you can get the behaviour you say you expect as follows:
Now callers should use bar instead of foo. If they have a C*, then they can cast it to A*, in which case
bar
will callC::foo
, or they can cast it to B*, in which casebar
will callB::foo
. C can override bar again if it wants, or else not bother, in which case callingbar()
on a C* callsB::foo()
as you'd expect.I don't know when anyone would want this behaviour, though. The whole point of virtual functions is to call the same function for a given object, no matter what base or derived class pointer you're using. C++ therefore assumes that if calls to a particular member function through a base class are virtual, then calls through derived classes should also be virtual.
当您声明
虚拟
方法时,您基本上是在虚函数表中添加一个新条目。重写虚拟
方法会更改该条目的值;它不会删除它。对于 Java 或 C# 等语言来说基本上也是如此。不同之处在于,使用 Java 中的final
关键字,您可以要求编译器任意强制无法覆盖它。 C++ 不提供这种语言功能。When you declare a
virtual
method, you're basically adding a new entry in the vtable. Overriding avirtual
method changes the value of that entry; it doesn't remove it. This is basically true for languages like Java or C# too. The difference is that, withfinal
keyword in Java, you can ask the compiler to arbitrarily enforce not being able to override it. C++ does not provide this language feature.仅仅因为类被迫拥有 vtable,并不意味着编译器被迫使用它。如果对象的类型是静态已知的,则编译器可以自由地绕过 vtable 作为优化。例如,在这种情况下,B::foo 可能会被直接调用:
不幸的是,我知道验证这一点的唯一方法是查看生成的汇编代码。
Just because the class is forced to have a vtable, doesn't mean the compiler is forced to use it. If the type of the object is known statically, the compiler is free to bypass the vtable as an optimization. For example, B::foo will probably be called directly in this situation:
Unfortunately the only way I know to verify this is to look at the generated assembly code.
因为从技术上讲,无论你做什么,它都是虚拟的——它在表中占有一席之地。剩下的就是语法执法,这就是 C++ 与 java 不同的地方。
Because technically it is virtual whatever you do — it has its place in the table. The rest would be a syntactical law enforcement and this is where C++ is different from java.
当定义第一个虚函数时,会为基类创建一个虚函数表。在您的示例中 foo() 在 vtable 中有一个条目。当派生类继承自基类时,它也会继承 vtable。派生类必须在其 vtable 中有一个 foo() 条目,以便当通过基类指针多态引用派生类时,调用将被正确重定向。
A vtable is created for the base class when the first virtual function is defined. In your example foo() has an entry in the vtable. When the derived class inherits from the base class it inherits the vtable as well. The derived class must have an entry for foo() in its vtable so that the call will be redirected appropriately when the derived class is referenced polymorphically through a base class pointer.
看来至少 Visual Studio 能够利用
final
关键字来跳过 vtable 查找,例如此代码:为
b.foo()
生成相同的代码并且对于bB::foo()
:如果没有
final
,它会使用查找表:不过,我不知道其他编译器是否也会这样做。
It seems that at least Visual Studio is able to take advantage of the
final
keyword to skip vtable lookup, eg this code:Produces the same code for
b.foo()
and forb.B::foo()
:Whereas without the
final
it uses the lookup table:I don't know whether other compilers do the same, though.