オーバーライドとデフォルト引数の罠

C++でclassを継承して仮想関数をオーバーライドする際に,その仮想関数のデフォルト引数を変更してはならない,という話です.この話はExceptional C++の項目21に載っています.

問題

以下のコードの実行結果を予想してみてください.

#include <iostream>
using namespace std;

class Base {
public:
  virtual ~Base() {}
  virtual void f(int i = 10) { // デフォルト引数あり
    cout << "Base::f    : " << i << endl;
  }
};

class Derived : public Base {
public:
  Derived() {}
  void f(int i = 20) { // デフォルト引数を変更してオーバーライド
    cout << "Derived::f : " << i << endl;
  }
};

int main(int argc, char const* argv[]) {
  Base b;
  Derived d;
  Base* pb = new Derived;

  // デフォルト引数を使ってfを呼び出す
  b.f();
  d.f();
  pb->f();

  delete pb; 
  return 0;
}

上のコードの実行結果は以下のようになります.

Base::f    : 10
Derived::f : 20
Derived::f : 10

1行目と2行目の出力は納得だと思いますが,3行目の出力に首をかしげる人が多いんじゃないかと思います.pbはDerivedのオプジェクトを指していて,Derivedは関数fをオーバーライドしてデフォルト引数をi = 20にしてるんだから,pb->f();の出力は"Derived::f : 20"となるはずだ,と.それなのに,f自体はDerivedのものが呼ばれているにもかかわらずデフォルト引数はBaseのものが使われているのは何故だ,と.

原因

実は,C++のデフォルト引数の解決は動的な型(Derived)ではなく静的な型(Base)によって行われます.上のコードの場合のデフォルト引数の解決は以下のような感じになります.

  1. pb->f()
  2. fの呼び出しに引数がないからデフォルト引数を付けよう
  3. pbの型Base*だ(*pbがDerivedだというところまでは確認しない)
  4. Base::fのデフォルト引数は10だ
  5. pb->f(10)

そして,

  1. pb->f(10)
  2. *pbの型はDerivedだ
  3. Derived::fをi = 10で呼び出そう

となるわけです.このことを知らずにDerivedのデフォルト引数に期待してポリモーフィズムを用いたプログラムを書いていると痛い目を見ることになります.

結論

こうなることを防ぐためにも,

オーバーライドした仮想関数のデフォルト引数を変更してはならない.

これに尽きます.