仮想関数の戻り値の型は、基本クラスの型と同じか、共変でなければなりません。しかし、なぜこの制限があるのでしょうか?
4 に答える
続くナンセンスのために:
struct foo
{
virtual int get() const { return 0; }
};
struct bar : foo
{
std::string get() const { return "this certainly isn't an int"; }
};
int main()
{
bar b;
foo* f = &b;
int result = f->get(); // int, right? ...right?
}
派生クラスがまったく関係のないものを返すのは賢明ではありません。
戻り値を使用しているコードは、あらゆる種類の無関係な型が戻ってくることにどのように対処するのでしょうか? 例えば:
class A
{
public:
virtual float func();
};
class B: public A
{
public:
virtual char *func();
};
A *p = (some_condition) ? new A() : new B();
p->func(); // Oh no! What is the type?
C++ 標準によると:
オーバーライド関数の戻り値の型は、オーバーライドされた関数の戻り値の型と同じか、関数のクラスと共変でなければなりません。関数 D::f が関数 B::f をオーバーライドする場合、次の基準を満たす場合、関数の戻り値の型は共変です。
1)どちらもクラスへのポインタまたはクラスへの参照です
2) B::f の戻り値の型のクラスは、D::f の戻り値の型のクラスと同じクラスであるか、D::f の戻り値の型のクラスの明確でアクセス可能な直接または間接の基本クラスです。 ::f
3)ポインタまたは参照の両方が同じ cv-qualification を持ち、D::f の戻り値の型のクラス型が、B::f の戻り値の型のクラス型と同じ cv-qualification または少ない cv-qualification を持っている.
答えは、 「vector<Apple*>をvector<Fruit*>に割り当てられないのはなぜですか?」の答えと非常によく似ています。BjarneStroustrupのFAQで。
戻り型を変更する機能は、ポリモーフィック型を処理するときに、言語の型安全性に穴を開けることになります(具体的な例については、@ GManNickGからの回答を参照してください)。
戻り型に影響を与えることが理想的である場合、かなり一般的な状況が1つあります。それは、基本型の仮想メソッドからポリモーフィック・ポインターを返す場合です。例えば、
class Base {
public:
virtual Base* parent() = 0;
};
class Child : public Base {
public:
Base* parent() override
{
return parent_;
}
private:
Parent* parent_; // Assume `Parent` type exists.
};
ここで、そのメンバーChild
について知っている型情報を失いましたparent_
。タイプが一時的に明確に定義されていたとしても、これは多くのキャストにつながります。これは、 Curiously Recurring Template Parameter(CRTP)イディオムを使用して解決できます。
template<class ParentType>
class Base {
public:
virtual ParentType* parent()
{
return parent_;
}
private:
ParentType* parent_;
};
class Child : public Base<Parent> {
};