名稱空間
變體
操作

virtual 函式說明符

來自 cppreference.com
< cpp‎ | 語言
 
 
C++ 語言
 
 

virtual 說明符指定非靜態成員函式是“虛”的並支援動態分派。它只能出現在非靜態成員函式初始宣告的decl-specifier-seq中(即在類定義中宣告時)。

目錄

[編輯] 解釋

虛擬函式是成員函式,其行為可以在派生類中被重寫。與非虛擬函式不同,即使在編譯時沒有關於類的實際型別的資訊,重寫行為也會保留。也就是說,如果使用指向基類的指標或引用來處理派生類,則對重寫的虛擬函式的呼叫將呼叫派生類中定義的行為。這種函式呼叫被稱為“虛擬函式呼叫”或“虛呼叫”。如果使用限定名稱查詢選擇函式(即,如果函式名稱出現在作用域解析運算子::的右側),則虛擬函式呼叫被抑制。

#include <iostream>
 
struct Base
{
    virtual void f()
    {
        std::cout << "base\n";
    }
};
 
struct Derived : Base
{
    void f() override // 'override' is optional
    {
        std::cout << "derived\n";
    }
};
 
int main()
{
    Base b;
    Derived d;
 
    // virtual function call through reference
    Base& br = b; // the type of br is Base&
    Base& dr = d; // the type of dr is Base& as well
    br.f(); // prints "base"
    dr.f(); // prints "derived"
 
    // virtual function call through pointer
    Base* bp = &b; // the type of bp is Base*
    Base* dp = &d; // the type of dp is Base* as well
    bp->f(); // prints "base"
    dp->f(); // prints "derived"
 
    // non-virtual function call
    br.Base::f(); // prints "base"
    dr.Base::f(); // prints "base"
}

[編輯] 詳情

如果在類Base中某個成員函式vf被宣告為virtual,並且某個類Derived(直接或間接派生自Base)有一個成員函式宣告,該函式具有相同的

  • 名稱
  • 引數型別列表(但不包括返回型別)
  • cv-限定符
  • ref-限定符

那麼類Derived中的這個函式也是“虛”的(無論其宣告中是否使用關鍵字virtual),並且“重寫”Base::vf(無論其宣告中是否使用關鍵字override)。

Base::vf不需要可訪問或可見才能被重寫。(Base::vf可以宣告為private,或者Base可以使用private繼承。Derived的基類中任何同名成員(其繼承自Base)都不影響重寫判斷,即使它們會在名稱查詢期間隱藏Base::vf。)

class B
{
    virtual void do_f(); // private member
public:
    void f() { do_f(); } // public interface
};
 
struct D : public B
{
    void do_f() override; // overrides B::do_f
};
 
int main()
{
    D d;
    B* bp = &d;
    bp->f(); // internally calls D::do_f();
}

對於每個虛擬函式,都有“最終重寫者”,在進行虛擬函式呼叫時執行。基類Base的虛成員函式vf是最終重寫者,除非派生類宣告或繼承(透過多重繼承)另一個重寫vf的函式。

struct A { virtual void f(); };     // A::f is virtual
struct B : A { void f(); };         // B::f overrides A::f in B
struct C : virtual B { void f(); }; // C::f overrides A::f in C
 
struct D : virtual B {}; // D does not introduce an overrider, B::f is final in D
 
struct E : C, D          // E does not introduce an overrider, C::f is final in E
{
    using A::f; // not a function declaration, just makes A::f visible to lookup
};
 
int main()
{
    E e;
    e.f();    // virtual call calls C::f, the final overrider in e
    e.E::f(); // non-virtual call calls A::f, which is visible in E
}

如果一個函式有多個最終重寫者,則程式格式錯誤。

struct A
{
    virtual void f();
};
 
struct VB1 : virtual A
{
    void f(); // overrides A::f
};
 
struct VB2 : virtual A
{
    void f(); // overrides A::f
};
 
// struct Error : VB1, VB2
// {
//     // Error: A::f has two final overriders in Error
// };
 
struct Okay : VB1, VB2
{
    void f(); // OK: this is the final overrider for A::f
};
 
struct VB1a : virtual A {}; // does not declare an overrider
 
struct Da : VB1a, VB2
{
    // in Da, the final overrider of A::f is VB2::f
};

具有相同名稱但不同引數列表的函式不會重寫同名的基函式,而是“隱藏”它:當非限定名稱查詢檢查派生類的作用域時,查詢會找到該宣告,而不會檢查基類。

struct B
{
    virtual void f();
};
 
struct D : B
{
    void f(int); // D::f hides B::f (wrong parameter list)
};
 
struct D2 : D
{
    void f(); // D2::f overrides B::f (doesn't matter that it's not visible)
};
 
int main()
{
    B b;
    B& b_as_b = b;
 
    D d;
    B& d_as_b = d;
    D& d_as_d = d;
 
    D2 d2;
    B& d2_as_b = d2;
    D& d2_as_d = d2;
 
    b_as_b.f();  // calls B::f()
    d_as_b.f();  // calls B::f()
    d2_as_b.f(); // calls D2::f()
 
    d_as_d.f();  // Error: lookup in D finds only f(int)
    d2_as_d.f(); // Error: lookup in D finds only f(int)
}

如果一個函式用說明符override宣告,但沒有重寫虛擬函式,則程式格式錯誤。

struct B
{
    virtual void f(int);
};
 
struct D : B
{
    virtual void f(int) override;  // OK, D::f(int) overrides B::f(int)
    virtual void f(long) override; // Error: f(long) does not override B::f(int)
};

如果一個函式用說明符final宣告,而另一個函式試圖重寫它,則程式格式錯誤。

struct B
{
    virtual void f() const final;
};
 
struct D : B
{
    void f() const; // Error: D::f attempts to override final B::f
};
(C++11 起)

非成員函式和靜態成員函式不能是虛擬函式。

函式模板不能宣告為virtual。這僅適用於函式本身是模板的情況——類模板的普通成員函式可以宣告為虛擬函式。

虛擬函式(無論是宣告為virtual還是重寫一個)不能有任何相關的約束。

struct A
{
    virtual void f() requires true; // Error: constrained virtual function
};

consteval虛擬函式不得重寫或被非consteval虛擬函式重寫。

(C++20 起)

虛擬函式的預設引數在編譯時替換。

[編輯] 協變返回型別

如果函式Derived::f重寫函式Base::f,它們的返回型別必須相同或為“協變”。如果滿足以下所有要求,則兩種型別是協變的:

  • 兩種型別都是指向類的指標或引用(左值或右值)。不允許有多級指標或引用。
  • Base::f()返回型別中引用的/指向的類必須是Derived::f()返回型別中引用的/指向的類的明確且可訪問的直接或間接基類。
  • Derived::f()的返回型別必須與Base::f()的返回型別具有相同或更低的cv-限定符

Derived::f返回型別中的類必須是Derived本身,或者在Derived::f宣告時必須是完整型別

當進行虛擬函式呼叫時,最終重寫者返回的型別會隱式轉換為被呼叫的重寫函式的返回型別。

class B {};
 
struct Base
{
    virtual void vf1();
    virtual void vf2();
    virtual void vf3();
    virtual B* vf4();
    virtual B* vf5();
};
 
class D : private B
{
    friend struct Derived; // in Derived, B is an accessible base of D
};
 
class A; // forward-declared class is an incomplete type
 
struct Derived : public Base
{
    void vf1();    // virtual, overrides Base::vf1()
    void vf2(int); // non-virtual, hides Base::vf2()
//  char vf3();    // Error: overrides Base::vf3, but has different
                   // and non-covariant return type
    D* vf4();      // overrides Base::vf4() and has covariant return type
//  A* vf5();      // Error: A is incomplete type
};
 
int main()
{
    Derived d;
    Base& br = d;
    Derived& dr = d;
 
    br.vf1(); // calls Derived::vf1()
    br.vf2(); // calls Base::vf2()
//  dr.vf2(); // Error: vf2(int) hides vf2()
 
    B* p = br.vf4(); // calls Derived::vf4() and converts the result to B*
    D* q = dr.vf4(); // calls Derived::vf4() and does not convert the result to B*
}

[編輯] 虛解構函式

儘管解構函式不會被繼承,但如果基類將其解構函式宣告為virtual,則派生解構函式總是會重寫它。這使得可以透過指向基類的指標刪除多型型別的動態分配物件。

class Base
{
public:
    virtual ~Base() { /* releases Base's resources */ }
};
 
class Derived : public Base
{
    ~Derived() { /* releases Derived's resources */ }
};
 
int main()
{
    Base* b = new Derived;
    delete b; // Makes a virtual function call to Base::~Base()
              // since it is virtual, it calls Derived::~Derived() which can
              // release resources of the derived class, and then calls
              // Base::~Base() following the usual order of destruction
}

此外,如果基類的解構函式不是虛擬函式,則透過指向基類的指標刪除派生類物件是“未定義行為”,無論是否存在如果派生解構函式未被呼叫就會洩漏的資源,除非選定的解除分配函式是銷燬性operator delete(C++20 起)

一條有用的準則是,任何基類的解構函式必須是public 和虛擬函式,或 protected 和非虛擬函式,只要涉及刪除表示式,例如在std::unique_ptr中隱式使用時(C++11 起)

[編輯] 構造與析構期間

當虛擬函式直接或間接從建構函式或解構函式(包括在類的非靜態資料成員的構造或析構期間,例如在成員初始化列表中)被呼叫時,並且呼叫所適用的物件是正在構造或析構的物件,則被呼叫的函式是建構函式或解構函式類中的最終重寫者,而不是更派生類中重寫它的函式。換句話說,在構造或析構期間,更派生類不存在。

在構造具有多個分支的複雜類時,屬於一個分支的建構函式內部,多型性僅限於該類及其基類:如果它獲取指向此子層次結構之外的基子物件的指標或引用,並嘗試呼叫虛擬函式(例如,使用顯式成員訪問),則行為是未定義的。

struct V
{
    virtual void f();
    virtual void g();
};
 
struct A : virtual V
{
    virtual void f(); // A::f is the final overrider of V::f in A
};
 
struct B : virtual V
{
    virtual void g(); // B::g is the final overrider of V::g in B
    B(V*, A*);
};
 
struct D : A, B
{
    virtual void f(); // D::f is the final overrider of V::f in D
    virtual void g(); // D::g is the final overrider of V::g in D
 
    // note: A is initialized before B
    D() : B((A*) this, this) {}
};
 
// the constructor of B, called from the constructor of D 
B::B(V* v, A* a)
{
    f(); // virtual call to V::f (although D has the final overrider, D doesn't exist)
    g(); // virtual call to B::g, which is the final overrider in B 
 
    v->g(); // v's type V is base of B, virtual call calls B::g as before
 
    a->f(); // a’s type A is not a base of B. it belongs to a different branch of the
            // hierarchy. Attempting a virtual call through that branch causes
            // undefined behavior even though A was already fully constructed in this
            // case (it was constructed before B since it appears before B in the list
            // of the bases of D). In practice, the virtual call to A::f will be
            // attempted using B's virtual member function table, since that's what
            // is active during B's construction)
}

[編輯] 關鍵字

virtual

[編輯] 缺陷報告

下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。

缺陷報告 應用於 釋出時的行為 正確的行為
CWG 258 C++98 派生類的非 const 成員函式可能變為
虛擬函式,因為其基類的 const 虛成員函式
虛屬性也要求 cv-
限定符相同
CWG 477 C++98 友元宣告可能包含virtual說明符 不允許
CWG 1516 C++98 未提供“虛擬函式呼叫”
和“虛呼叫”的定義
已提供

[編輯] 參閱

派生類和繼承模式
override 說明符 (C++11) 顯式宣告方法重寫另一個方法[編輯]
final 說明符 (C++11) 宣告方法不能被重寫或類不能被繼承[編輯]