C ++仮想関数の戻り値の型


回答:


86

場合によっては、はい、戻り値の型が元の戻り値の型と共変である限り、派生クラスが異なる戻り値の型を使用して仮想関数をオーバーライドすることが合法です。たとえば、次のことを考慮してください。

class Base {
public:
    virtual ~Base() {}

    virtual Base* clone() const = 0;
};

class Derived: public Base {
public:
    virtual Derived* clone() const {
        return new Derived(*this);
    }
};

ここでBaseは、cloneを返すと呼ばれる純粋仮想関数を定義しますBase *。派生実装では、この仮想関数は戻り値の型を使用してオーバーライドされDerived *ます。戻り値の型はベースと同じではありませんが、いつでも書くことができるので、これは完全に安全です。

Base* ptr = /* ... */
Base* clone = ptr->clone();

toclone()を呼び出すと、常にBaseオブジェクトへのポインタが返されますDerived*。これは、を返しても、このポインタは暗黙的にaに変換可能でBase*あり、操作は明確に定義されているためです。

より一般的には、関数の戻り値の型がそのシグネチャの一部と見なされることはありません。戻り値の型が共変である限り、任意の戻り値の型でメンバー関数をオーバーライドできます。


9
この「メンバー関数は任意の戻り値の型でオーバーライドできます」は正しくありません。戻り値の型が同一または共変(説明した)である限り、期間をオーバーライドできます。ここではこれ以上一般的なケースはありません。
bronekk 2012年

1
@ bronekk-引用した文の残りの部分では、新しい戻り値の型は元の型のどこでも使用できる必要があると述べています。つまり、新しいタイプは元のタイプと共変です。
templatetypedef

文の残りの部分は正しくありません。交換想像Base*してlongDerived*してint(あるいは他の方法で回避、関係ありません)。それは動作しません。
bronekk 2012年

@ bronekk-ああ、そうだとは思わなかった!それを指摘してくれてありがとう。
templatetypedef

1
型が元の型のどこでも使用可能であり、共分散が2つの異なるコンテキストであること。共変とは、関数を実装する型間の関係と、返される型間の関係が同じように変化することを意味します。反変(C ++では役に立ちませんが)は反対のコンテキストです。一部の言語では、動的ディスパッチで反変引数を使用できます(ベースがT型のオブジェクトを取得する場合、派生型はT 'を取得できます。T'はTのベースです。一方の階層を下に移動すると、もう一方の階層を上に移動します。 )。
デビッド・ロドリゲス-ドリビアス2012

54

はい。戻り値の型は、共変である限り、異なっていてもかまいません。C ++標準では、次のように説明されています(§10.3/ 5)。

オーバーライドする関数の戻り値の型は、オーバーライドされる関数の戻り値の型と同じであるか、関数のクラスと共変である必要があります。関数が関数をD::fオーバーライドするB::f場合、次の基準を満たしていれば、関数の戻り値の型は共変です。

  • どちらもクラスへのポインタまたはクラスへの参照です98)
  • の戻り値の型のB::fクラスは、D::fまたはの戻り値の型のクラスと同じクラスであり、の戻り値の型のクラスの明確な直接または間接の基本クラスでありD::f、でアクセス可能です。D
  • ポインタまたは参照の両方が同じcv-qualificationを持ち、の戻り値の型のクラスタイプは、の戻り値の型のクラスタイプD::fと同じcv-qualificationまたはそれ以下のcv-qualificationを持ちB::fます。

脚注98は、「クラスへのマルチレベルポインタまたはクラスへのマルチレベルポインタへの参照は許可されていない」と指摘しています。

要するに、Dがのサブタイプである場合B、の関数Dの戻り値の型は、の関数の戻り値の型のサブタイプである必要がありますB。戻り値の型は、自身が基づいているとき、最も一般的な例があるDB、彼らがする必要はありません。これを考えてみましょう。ここでは、2つの別々のタイプ階層があります。

struct Base { /* ... */ };
struct Derived: public Base { /* ... */ };

struct B {
  virtual Base* func() { return new Base; }
  virtual ~B() { }
};
struct D: public B {
  Derived* func() { return new Derived; }
};

int main() {
  B* b = new D;
  Base* base = b->func();
  delete base;
  delete b;
}

これが機能する理由は、の呼び出し元がポインタをfunc期待しているためBaseです。どんなBaseポインタでもかまいません。したがって、D::func常にDerivedポインタを返すことが約束されている場合、任意のDerivedポインタを暗黙的にBaseポインタに変換できるため、祖先クラスによって設定されたコントラクトを常に満たします。したがって、発信者は常に期待どおりの結果を得ることができます。


一部の言語では、戻り値の型を変更できることに加えて、オーバーライドする関数のパラメーター型も変更できます。彼らがそれをするとき、彼らは通常反変である必要があります。あれば、されてB::f受け入れDerived*、その後、D::f受け入れることを許可されますBase*。子孫は、受け入れるものを緩くし、返すものを厳しくすることができます。C ++では、パラメーター型の反変性は許可されていません。パラメータタイプを変更すると、C ++はそれをまったく新しい関数と見なすため、オーバーロードと非表示になり始めます。このトピックの詳細については、ウィキペディアの共変性と反変性(コンピューターサイエンス)を参照してください。


2
これは実際の機能ですか、それとも解像度で使用されていない戻り値の型の副作用ですか?
マーティンヨーク

1
@マーティン、間違いなく機能。過負荷の解決はそれとは何の関係もないと私はかなり確信しています。関数をオーバーライドする場合、戻り値の型使用されます。
ロブ・ケネディ

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.