名前空間
変種
操作

virtual 関数指定子

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
仮想関数
override指定子 (C++11)  
final指定子 (C++11)
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

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でメンバ関数vfvirtualとして宣言され、Baseから直接的または間接的に派生したクラスDerivedが、同じメンバ関数宣言を持つ場合、

  • name
  • パラメータ型リスト(ただし戻り値型は除く)
  • cv-修飾子
  • ref 修飾子

その場合、クラスDerived内のこの関数も仮想であり(宣言でキーワードvirtualが使用されているかどうかにかかわらず)、Base::vf上書きします(宣言でキーワードoverrideが使用されているかどうかにかかわらず)。

Base::vfは、上書きされるためにアクセス可能または可視である必要はありません。(Base::vfはprivateとして宣言されるか、Baseはprivate継承を使用して継承されることができます。Baseを継承するDerivedの基底クラス内の同じ名前のメンバは、名前検索中に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を上書きする場合、それらの戻り値型は同じであるか、共変でなければなりません。2つの型が共変であるのは、以下の要件をすべて満たす場合です。

  • 両方の型がクラスへのポインタまたは参照(左辺値または右辺値)であること。多段階のポインタまたは参照は許可されません。
  • 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以降)

有用な指針として、delete式が関係する場合、例えばstd::unique_ptrで暗黙的に使用される場合など(C++11以降)、すべての基底クラスのデストラクタはpublicかつvirtualであるか、protectedかつ非virtualであるべきです。

[編集] 構築中および破棄中

コンストラクタまたはデストラクタ(クラスの非静的データメンバの構築または破棄中、例えばメンバ初期化リスト内を含む)から直接的または間接的に仮想関数が呼び出され、その呼び出しが適用されるオブジェクトが構築中または破棄中のオブジェクトである場合、呼び出される関数はコンストラクタまたはデストラクタのクラスにおける最終オーバーライダであり、より派生したクラスでそれをオーバーライドするものではありません。言い換えれば、構築中または破棄中、より派生したクラスは存在しません。

複数のブランチを持つ複雑なクラスを構築する場合、あるブランチに属するコンストラクタ内では、多態性はそのクラスとその基底に制限されます。このサブ階層外の基底サブオブジェクトへのポインタまたは参照を取得し、仮想関数呼び出し(例えば明示的なメンバアクセスを使用)を試みると、動作は未定義になります。

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++標準に遡って適用されました。

DR 適用対象 公開された動作 正しい動作
CWG 258 C++98 派生クラスの非constメンバ関数が
その基底クラスのconst仮想メンバ関数により仮想になる可能性
仮想性にはcv-
修飾も同じである必要がある
CWG 477 C++98 フレンド宣言にvirtual指定子を含めることができる 許可されなくなった。
CWG 1516 C++98 "仮想関数呼び出し"という用語の定義
および"仮想呼び出し"が提供されていなかった
提供された

[編集] 関連項目

派生クラスと継承モード
override 指定子 (C++11) あるメソッドが別のメソッドをオーバーライドすることを明示的に宣言する[編集]
final指定子 (C++11) メソッドがオーバーライドできないこと、またはクラスが派生できないことを宣言します[編集]
English 日本語 中文(简体) 中文(繁體)