C++では、一般的に戻り値だけが異なるメソッドをオーバーライドすることはできません。
1 2 3 4 5 6 7 |
struct Base { virtual int func() = 0; }; struct Derived : public Base { float func() override; // エラー!戻り値型が違うぞ! }; |
引数が違えば別関数と扱われ、単にメソッドが増えるだけなのですが、引数が同じ場合はオーバーライド対象なので、このコードはコンパイルエラーとなります。
ただし、戻り値の型が異なっていても、その型が共変型であるならばコンパイルエラーにはなりません。
オーバーライドする仮想関数の戻り値の型は、オーバーライド元の関数のものと同じか、または「共変(covariant)」でなければならない。
__C++ランゲージクイックリファレンス
……聞き慣れないですね。
要するに「戻り値の型がクラス型の参照かポインタで、かつis-a関係にあるもの」に関しては親クラスと異なる型の戻り値であってもオーバーライドできますよっていう話です。
例を見てみましょう。
何はともあれ、まずはis-a関係を持つ型を用意します。
1 2 3 4 |
struct Shape {}; // Shapeとis-a関係にあるBox struct Box : public Shape {}; |
先ほどのエラーになったオーバーライドの例の戻り値に今定義した型ShapeとBoxを指定します。
1 2 3 4 5 6 7 |
struct Base { virtual Shape* get() = 0; }; struct Derived : public Base { Box* get() override; // こんどは戻り値の型が共変なのでOK! }; |
コンパイルは通りますが、共変型を持つメソッドはオーバーライドの挙動が通常のオーバーライドと異なるので注意が必要です。
以下のプログラムでは結果として”shape”と表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
#include <iostream> // Shapeを定義 struct Shape { void print() { printf("shape"); } }; // Shapeから継承したBoxを定義 struct Box : public Shape { void print() { printf("box"); } }; struct Base { virtual ~Base(){} virtual Shape* get() = 0; }; struct Derived : public Base { // 純粋仮想関数getをオーバーライドしてBox*を返す // 基底クラスと戻り値の型が違うけど共変型なのでOK Box* get() override { return new Box(); } }; int main() { Base* b = new Derived; // Derived生成 auto a = b->get(); // ポリモーフィックにget()を呼び出す a->print(); // Boxって表示されるかな? delete b; return 0; } |
Boxと表示されそうな気がしますが、実際にはshapeと表示されます。
ただし、ここが超重要なのですが、Shape*を返してもb->get()は決してBase::get()の呼び出しでは無いということです。
ポリモーフィズムの挙動に準じて、あくまでDerived::get()が呼び出されますが、操作しているポインタbの型次第で、Derived::get()で指定した型Box*のまま戻り値が返されるか、Base::get()で指定した型に暗黙に変換された後、返されるかという挙動の違いが発生します。
慣れないとちょっと分かりづらいですね。
むしろ結構分かりづらいですね。
ひょっとしたらC++の挙動の中でトップレベルの分かりづらさかもしれないですね。
一応、共変型に対してオーバーライドのルールが緩和される為、純粋仮想関数による子クラスへの関数定義の強制のメリットと実際に取得する型が親クラスに引っ張られないメリットを享受できる……ということになっています。
自分自身の型を返す関数を定義する時など、親クラス固定だと、受け取った側でのキャストが面倒ですしね。
共変型の戻り値には、常に適切な抽象レベルで作業できるという利点があります。(中略)共変型の戻り値を使用すれば、ミスを誘いがちなキャストを使用したために、そもそも失うはずの無かった型情報を再度提供するはめになることもありません。
__C++標準的コーディング技法#31 戻り値の共変型
まとめ
- 戻り値の型が違うメソッドはオーバーライドできません
- ただし共変型の場合のみこのルールが緩和される
- 操作している型のタイプによって戻り値の型は暗黙に変換される
- 型が暗黙に変換されるだけで関数の呼び出し自体はポリモーフィック