web-dev-qa-db-ja.com

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

継承されたクラスは、異なる戻り値型(戻り値としてテンプレートを使用しない)で仮想関数を実装できますか?

72
Bob

場合によっては、はい、戻り値のタイプがcovariantである限り、派生クラスが別の戻り値のタイプを使用して仮想関数をオーバーライドすることは正当です。たとえば、次のことを考慮してください。

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

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

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

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

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

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

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

76
templatetypedef

はい。戻り値の型は、 covariant である限り、異なっていてもかまいません。 C++標準では、このように記述されています(§10.3/ 5):

オーバーライド関数の戻り値の型は、オーバーライドされた関数の戻り値の型と同じか、関数のクラスでcovariantでなければなりません。関数D::fが関数B::fをオーバーライドする場合、以下の基準を満たす場合、関数の戻り値の型は共変です。

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

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

つまり、DBのサブタイプである場合、Dの関数の戻り値の型は、Bの関数の戻り値の型のサブタイプである必要があります。最も一般的な例は、戻り値の型自体がDおよびBに基づいている場合ですが、そうである必要はありません。これについて考えてみましょう。ここでは、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ポインターに変換できるため、祖先クラスによってレイアウトされたコントラクトを常に満たします。したがって、発信者は常に期待どおりのものを取得します。


一部の言語では、戻り値の型を変更できることに加えて、オーバーライド関数のパラメータの型も変更できます。それを行う場合、通常はconvarivariantである必要があります。つまり、B::fDerived*を受け入れる場合、D::fBase*を受け入れることができます。子孫はlooserを受け入れ、stricter彼らが返すもので。 C++では、パラメータータイプの矛盾を許可していません。パラメータタイプを変更すると、C++はそれをまったく新しい関数と見なすため、オーバーロードと非表示になります。このトピックの詳細については、Wikipediaの 共分散と反分散(コンピューターサイエンス) を参照してください。

48
Rob Kennedy

仮想関数の派生クラスの実装には、 Covariant Return Type を含めることができます。

2