仮想関数の継承とデフォルトパラメータの関係

デフォルトパラメータを持つ仮想関数を継承する場合の振る舞いを調べてみた.仮想関数は「動的に結合」される一方,デフォルトパラメータは「静的に結合」される
例えば,以下の簡単なクラスを考えよう:

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(); はコンパイルエラーとなることに注意しないとならない.継承したデフォルトパラメータを再定義しない方がよいが,継承先でもデフォルトパラメータを有効にしたい場合は,「同じデフォルトパラメータ」で関数を再定義する必要があるのである.