虛擬成員函式
成員函式也可以宣告為 virtual
。 在這種情況下,如果呼叫指標或對例項的引用,則不會直接訪問它們; 相反,他們將查詢虛擬函式表中的函式(虛擬函式的指向成員函式的列表,通常稱為 vtable
或 vftable
),並使用它來呼叫適合例項動態的版本(實際)型別。如果直接從類的變數呼叫函式,則不執行查詢。
struct Base {
virtual void func() { std::cout << "In Base." << std::endl; }
};
struct Derived : Base {
void func() override { std::cout << "In Derived." << std::endl; }
};
void slicer(Base x) { x.func(); }
// ...
Base b;
Derived d;
Base *pb = &b, *pd = &d; // Pointers.
Base &rb = b, &rd = d; // References.
b.func(); // Output: In Base.
d.func(); // Output: In Derived.
pb->func(); // Output: In Base.
pd->func(); // Output: In Derived.
rb.func(); // Output: In Base.
rd.func(); // Output: In Derived.
slicer(b); // Output: In Base.
slicer(d); // Output: In Base.
請注意,雖然 pd
是 Base*
,而 rd
是 Base&
,但是在兩個叫 Derived::func()
而不是 Base::func()
中的任何一個上呼叫 func()
; 這是因為 Derived
的 vtable
更新了 Base::func()
條目,而不是指向 Derived::func()
。相反,請注意如何將例項傳遞給 slicer()
總是會導致 Base::func()
被呼叫,即使傳遞的例項是 Derived
; 這是因為有一些稱為資料切片的東西,其中通過值將 Derived
例項傳遞到 Base
引數會使得 Derived
例項中不屬於 Base
例項的部分無法訪問。
當成員函式定義為 virtual 時,所有具有相同簽名的派生類成員函式都會覆蓋它,無論覆蓋函式是否指定為 virtual
。然而,這可能使得派生類更難以被程式設計師解析,因為沒有關於哪些函式是什麼的指示 23。
struct B {
virtual void f() {}
};
struct D : B {
void f() {} // Implicitly virtual, overrides B::f.
// You'd have to check B to know that, though.
};
但是請注意,派生函式只有在其簽名匹配時才會覆蓋基函式; 即使派生函式明確宣告為 virtual
,如果簽名不匹配,它也會建立一個新的虛擬函式。
struct BadB {
virtual void f() {}
};
struct BadD : BadB {
virtual void f(int i) {} // Does NOT override BadB::f.
};
Version >= C++ 11
從 C++ 11 開始,可以使用上下文相關關鍵字 override
明確覆蓋意圖。這告訴編譯器程式設計師希望它覆蓋基類函式,這會導致編譯器在沒有覆蓋任何內容時省略錯誤。
struct CPP11B {
virtual void f() {}
};
struct CPP11D : CPP11B {
void f() override {}
void f(int i) override {} // Error: Doesn't actually override anything.
};
這也有利於告訴程式設計師該函式既是虛擬的,又在至少一個基類中宣告,這可以使複雜的類更容易解析。
當一個函式宣告為 virtual
,並在類定義之外定義時,virtual
說明符必須包含在函式宣告中,而不是在定義中重複。
Version >= C++ 11
這也適用於 override
。
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
如果基類過載 virtual
函式,則只有明確指定為 virtual
的過載才是虛擬的。
struct BOverload {
virtual void func() {}
void func(int) {}
};
struct DOverload : BOverload {
void func() override {}
void func(int) {}
};
// ...
BOverload* bo = new DOverload;
bo->func(); // Calls DOverload::func().
bo->func(1); // Calls BOverload::func(int).
有關更多資訊,請參閱相關主題 。