When overriding a virtual member function, why does the overriding function always become virtual?
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() dir开发者_JS百科ectly and avoid a vtable lookup.
The standard does leave an opening to call B::foo directly and avoid a table lookup:
#include <iostream>
class A {
public: virtual void foo() = 0;
};
class B : public A {
public: void foo() {
std::cout <<"B::foo\n";
}
};
class C : public B {
public: void foo() {
std::cout <<"C::foo\n";
}
};
int main() {
C c;
A *ap = &c;
// virtual call to foo
ap->foo();
// virtual call to foo
static_cast<B*>(ap)->foo();
// non-virtual call to B::foo
static_cast<B*>(ap)->B::foo();
}
Output:
C::foo
C::foo
B::foo
So you can get the behaviour you say you expect as follows:
class A {
virtual void foo() = 0;
// makes a virtual call to foo
public: void bar() { foo(); }
};
class B : public A {
void foo() {
std::cout <<"B::foo\n";
}
// makes a non-virtual call to B::foo
public: void bar() { B::foo(); }
};
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 call C::foo
, or they can cast it to B*, in which case bar
will call B::foo
. C can override bar again if it wants, or else not bother, in which case calling bar()
on a C* calls B::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.
When you declare a virtual
method, you're basically adding a new entry in the vtable. Overriding a virtual
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, with final
keyword in Java, you can ask the compiler to arbitrarily enforce not being able to override it. C++ does not provide this language feature.
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:
B b;
b.foo();
Unfortunately the only way I know to verify this is to look at the generated assembly code.
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.
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.
It seems that at least Visual Studio is able to take advantage of the final
keyword to skip vtable lookup, eg this code:
class A {
public:
virtual void foo() = 0;
};
class B : public A {
public:
void foo() final {}
};
B original;
B& b = original;
b.foo();
b.B::foo();
Produces the same code for b.foo()
and for b.B::foo()
:
b.foo();
000000013F233AA9 mov rcx,qword ptr [b]
000000013F233AAE call B::foo (013F1B4F48h)
b.B::foo();
000000013F233AB3 mov rcx,qword ptr [b]
000000013F233AB8 call B::foo (013F1B4F48h)
Whereas without the final
it uses the lookup table:
b.foo();
000000013F893AA9 mov rax,qword ptr [b]
000000013F893AAE mov rax,qword ptr [rax]
000000013F893AB1 mov rcx,qword ptr [b]
000000013F893AB6 call qword ptr [rax]
b.B::foo();
000000013F893AB8 mov rcx,qword ptr [b]
000000013F893ABD call B::foo (013F814F48h)
I don't know whether other compilers do the same, though.
精彩评论