仮想関数(C++)
仮想関数:インデックス への移動
仮想関数を使うと、基底クラスで定義した関数を派生クラスで別バージョンとして使用できます。virtual キーワードを使って、基底クラスで仮想関数を宣言できます。通常の方法で関数プロトタイプを宣言した後、宣言の前に virtual キーワードを付けます。純粋関数(抽象クラスを自動的に宣言)を宣言するには、プロトタイプの前に virtual キーワードを付け、関数をゼロに設定します。
virtual int funct1(void); // A virtual function declaration. virtual int funct2(void) = 0; // A pure function declaration.
関数では、定義と純粋関数の指定を同時に宣言できません。
例:
struct C { virtual void f() { } = 0; // ill-formed };
次に示すのは、本文を定義するための唯一の正しい構文です。
struct TheClass { virtual void funct3(void) = 0; }; virtual void TheClass::funct3(void) { // Some code here. };
メモ: 純粋仮想関数についての詳細は、抽象クラスを参照してください。
仮想関数を宣言するときは、次のガイドラインに留意してください。
- メンバ関数であることが必要である。
- 他のクラスのフレンドとして宣言できる。
- 静的メンバにならない。
仮想関数は、派生クラスで再定義する必要はありません。基底クラスで 1 回定義すると、すべての呼び出しが基底関数にアクセスします。
派生クラスで仮想関数を再定義するには、基底クラスでの宣言と派生クラスでの宣言で、引数の数と型が一致することが必要です。戻り型だけが異なる仮想関数を再定義する方法については、以降のセクションで説明します。関数を再定義することを、基底クラスの関数を「オーバーライドする」と言います。
関数 int Base::Fun(int) および int Derived::Fun(int) は、仮想関数ではない場合でも宣言できます。このような場合、int Derived::Fun(int) は、基底クラスに存在する他のバージョンの Fun(int) を「隠す」と言います。さらに、Derived クラスが他のバージョンの Fun()(つまり、引数が異なる Fun())を定義する場合、このバージョンを Fun() のオーバーロードされたバージョンと言います。
仮想関数の戻り型
一般に、仮想関数を再定義するときに、関数の戻り型だけを変更することはできません。仮想関数を再定義する場合、(派生クラスでの)新しい定義と最初の宣言での、戻り型および仮パラメータは一致する必要があります。同じ名前の 2 つの関数が異なる仮パラメータをとる場合、C++ はそれらを異なるものとみなし、仮想関数として処理されません。
基底クラスの特定の仮想関数では、派生クラスでオーバーライドするものと、オーバーライドされたものと戻り型が異なっていてもかまいません。ただし、これは次の両方の条件が満たされる場合に限ります。
- オーバーライドされた仮想関数が基底クラスへのポインタまたは参照を返す。
- オーバーライドする関数が派生クラスへのポインタまたは参照を返す。
基底クラス B および(B からパブリックに派生した)クラス D の、それぞれに仮想関数 vf がある場合、vf がクラス D のオブジェクト d に対して呼び出されると、B へのポインタまたは参照を介してアクセスする場合であっても、D::vf() が呼び出されます。次に例を示します。
struct X {};// Base class. struct Y : X {};// Derived class. struct B { virtual void vf1(); virtual void vf2(); virtual void vf3(); void f(); virtual X* pf();// Return type is a pointer to base. This can // be overridden. }; class D : public B { public: virtual void vf1();// Virtual specifier is legal but redundant. void vf2(int);// Not virtual, since it's using a different // arg list. This hides B::vf2(). // char vf3();// Illegal: return-type-only change! void f(); Y* pf();// Overriding function differs only // in return type. Returns a pointer to // the derived class. }; void extf() { D d;// Instantiate D B* bp = &d;// Standard conversion from D* to B* // Initialize bp with the table of functions // provided for object d. If there is no entry for a // function in the d-table, use the function // in the B-table. bp->vf1(); // Calls D::vf1 bp->vf2(); // Calls B::vf2 since D's vf2 has different args bp->f(); // Calls B::f (not virtual) X* xptr = bp->pf();// Calls D::pf() and converts the result // to a pointer to X. D* dptr = &d; Y* yptr = dptr->pf();// Calls D::pf() and initializes yptr. // No further conversion is done. }
D 内のオーバーライド関数 vf1 は自動的に仮想関数になります。virtual 指定子は派生クラス内のオーバーライド関数の宣言で使用できます。他にクラス D から派生したクラスがある場合、virtual キーワードが必要です。クラス D から派生するクラスがない場合、virtual を使うと冗長です。
仮想関数呼び出しの解釈は、呼び出されたオブジェクトの型により決まります。非仮想関数呼び出しにおける解釈は、呼び出されたオブジェクトを示すポインタまたは参照の型のみで決まります。
仮想関数には、こうした多様性に対する代償が伴います。派生クラス内の各オブジェクトには、実行時に正しい関数を選択するために、各関数のテーブルへのポインタが必要です(遅延バインディング)。