[英]When overriding a virtual member function, why does the overriding function always become virtual?
我寫這樣的時候:
class A {
public: virtual void foo() = 0;
}
class B {
public: void foo() {}
}
... B :: foo()也變為虛擬。 這背后的理由是什么? 我希望它的行為類似於Java中的final
關鍵字。
補充:我知道這樣的工作方式和vtable如何工作:)問題是,為什么C ++標准委員會沒有留下開放直接調用B :: foo()並避免vtable查找。
該標准確實留下了一個直接調用B :: foo的開口並避免了表查找:
#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();
}
輸出:
C::foo
C::foo
B::foo
因此,您可以獲得您所期望的行為,如下所示:
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(); }
};
現在調用者應該使用bar而不是foo。 如果他們有一個C *,那么他們可以將它轉換為A *,在這種情況下, bar
將調用C::foo
,或者他們可以將其轉換為B *,在這種情況下bar
將調用B::foo
。 如果它想要,C可以再次覆蓋bar,否則不會打擾,在這種情況下,調用C *上的bar()
調用B::foo()
如你所料。
但我不知道何時會有人想要這種行為。 虛函數的重點是為給定對象調用相同的函數,無論您使用的是什么基類或派生類指針。 因此,C ++假定如果通過基類對特定成員函數的調用是虛擬的,那么通過派生類的調用也應該是虛擬的。
聲明virtual
方法時,基本上是在vtable中添加新條目。 覆蓋virtual
方法會更改該條目的值; 它不會刪除它。 對於像Java或C#這樣的語言來說,這基本上也是如此。 不同之處在於,使用Java中的final
關鍵字,您可以要求編譯器任意強制執行無法覆蓋它。 C ++不提供此語言功能。
僅僅因為該類被強制使用vtable,並不意味着編譯器被迫使用它。 如果對象的類型是靜態已知的,則編譯器可以自由地繞過vtable作為優化。 例如,在這種情況下可能會直接調用B :: foo:
B b;
b.foo();
不幸的是,我知道驗證這一點的唯一方法是查看生成的匯編代碼。
因為從技術上講,無論你做什么,它都是虛擬的 - 它在表格中占有一席之地。 其余的將是一個語法執法,這是C ++與java不同的地方。
在定義第一個虛函數時,為基類創建一個vtable。 在您的示例中,foo()在vtable中有一個條目。 當派生類繼承自基類時,它也繼承了vtable。 派生類必須在其vtable中具有foo()的條目,以便在通過基類指針以多態方式引用派生類時,將適當地重定向調用。
似乎至少Visual Studio能夠利用final
關鍵字來跳過vtable查找,例如這段代碼:
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();
為b.foo()
和bB::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)
而沒有final
它使用查找表:
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)
不過,我不知道其他編譯器是否也這樣做。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.