仮想関数の継承とデフォルトパラメータの関係
デフォルトパラメータを持つ仮想関数を継承する場合の振る舞いを調べてみた.仮想関数は「動的に結合」される一方,デフォルトパラメータは「静的に結合」される.
例えば,以下の簡単なクラスを考えよう:
class TBase { protected: int x; public: TBase (void) : x(0) {}; ~TBase (void) {}; int getX(void) const {return x;}; virtual void increment (int add=1) = 0; }; class TTest : public TBase { public: TTest (void) {}; ~TTest (void) {}; /*override*/void increment (int add=-1) {x+= add;}; };
このとき,
TTest t; TBase &b(t); b.increment();
を実行したとき, b.increment(); は TTest::increment を呼び出す(仮想関数).これが「動的結合」だ.ところが,このとき省略したパラメータとして使われるのは, TTest::increment のデフォルトパラメータ add=-1 ではなく, TBase::increment のデフォルトパラメータ add=1 なのだ.
だから,
TTest t; t.increment(); cout<<"t.getX()= "<<t.getX()<<endl; TBase &b(t); b.increment(); cout<<"b.getX()= "<<b.getX()<<endl;
を実行したとき,
t.getX()= -1 b.getX()= 0
という結果になる.これが「静的結合」である.
Scott Meyers (スコット・メイヤーズ): Effective C++, 吉川訳, アスキー出版局, 1998 によると,
38項 継承したデフォルトパラメータ値を再定義してはならない
とある.これを守らないと混乱を招くことは,上の例から理解できるだろう.
ひとつ付け加えると,デフォルトパラメータが静的に結合されるから, TTest::increment のデフォルトパラメータを省略すると上記の t.increment(); はコンパイルエラーとなることに注意しないとならない.継承したデフォルトパラメータを再定義しない方がよいが,継承先でもデフォルトパラメータを有効にしたい場合は,「同じデフォルトパラメータ」で関数を再定義する必要があるのである.