34

この質問は数時間前にここで尋ねられ、私が自分のコードで共変の戻り型を実際に使用したことは一度もないことに気づきました。共分散が何であるかわからない場合は、タイプが同じ継承階層の一部である場合、(通常は)仮想関数の戻りタイプを異ならせることができます。例えば:

struct A {
   virtual ~A();
   virtual A * f();
   ...
};

struct B : public A {
   virtual B * f();
   ...
};

2つのf()関数の異なる戻り型は、共変であると言われます。古いバージョンのC++では、戻り値の型が同じである必要があるため、Bは次のようになります。

struct B : public A {
   virtual A * f();
   ...
};

だから、私の質問:共変の戻り型の仮想関数が必要な実際の例を持っている人はいますか、または単にベースポインタまたは参照を返すための優れたソリューションを作成しますか?

4

6 に答える 6

36

標準的な例は.clone()/.copy()メソッドです。だからいつでもできる

 obj = obj->copy();

obj の型に関係なく。

編集: この clone メソッドは、オブジェクト基本クラスで定義されます (実際には Java にあるため)。したがって、クローンが共変でない場合は、キャストする必要があるか、ルート基本クラスのメソッドに制限されます (コピーのソース オブジェクトのクラスと比較して、メソッドはほとんどありません)。

于 2009-08-11T14:37:51.770 に答える
15

一般に、共分散により、派生クラスのインターフェイスでは、基本クラスのインターフェイスよりも多くの情報を表現できます。派生クラスの動作は基本クラスの動作よりも具体的であり、共分散は違い (の 1 つの側面) を表します。

一部のクライアントが基本クラス インターフェイスを使用したいが、他のクライアントは派生クラス インターフェイスを使用する状況で、ガビンの関連する階層がある場合に役立ちます。const-correctness を省略した場合:

class URI { /* stuff */ };

class HttpAddress : public URI {
    bool hasQueryParam(string);
    string &getQueryParam(string);
};

class Resource {
    virtual URI &getIdentifier();
};

class WebPage : public Resource {
    virtual HttpAddress &getIdentifier();
};

自分が WebPage を持っていることを知っているクライアント (おそらくブラウザー) は、クエリ パラメータを調べることに意味があることを知っています。Resource 基本クラスを使用しているクライアントは、そのようなことを知りません。それらは常に返さHttpAddress&れたものをURI&変数または一時にバインドします。

Resource オブジェクトに HttpAddress があると思われるが、わからない場合は、dynamic_cast. しかし、共分散は、静的型付けがまったく有用であるのと同じ理由で、「知っているだけ」でキャストを行うよりも優れています。

代替手段があります-getQueryParam関数をオンにしますURIhasQueryParam、すべてに対してfalseを返すようにします(URIインターフェースを乱雑にします)。WebPage::getIdentifierreturn を定義したままにしURL&、実際にはを返し、HttpIdentifier&発信者に無意味なことをさせますdynamic_cast(呼び出しコードを混乱させ、「返された URL は HttpAddress に動的にキャスト可能であることが保証されている」と言う WebPage のドキュメント)。getHttpIdentifierに機能を追加しますWebPage(インターフェースを乱雑にしWebPageます)。または、それが意図していることに共分散を使用するだけWebPageです。FtpAddressMailtoAddressHttpAddress

最後に、もちろん、関連するガビンの階層は言うまでもなく、ガビンの階層を持つべきではないという合理的な議論があります。しかし、これらのクラスは、純粋仮想メソッドとのインターフェイスとして簡単に使用できるため、共分散を使用する有効性には影響しないと思います。

于 2009-08-11T16:04:54.480 に答える
5

基本クラスではなく特定のクラスを返すファクトリ メソッドを宣言するときに、共分散が役立つと思います。 この記事では、このシナリオについて詳しく説明しており、次のコード例が含まれています。

class product
{
    ...
};

class factory
{
public:
    virtual product *create() const = 0;
    ...
};

class concrete_product : public product
{
    ...
};

class concrete_factory : public factory
{
public:
    virtual concrete_product *create() const
    {
        return new concrete_product;
    }
    ...
};
于 2009-08-11T15:01:48.050 に答える
2

static_castsを取り除くために既存のコードを操作するとき、私はしばしば共分散を使用していることに気付きます。通常、状況は次のようになります。

class IPart {};

class IThing {
public:
  virtual IPart* part() = 0;
};

class AFooPart : public IPart {
public:
  void doThis();
};

class AFooThing : public IThing {
  virtual AFooPart* part() {...}
};

class ABarPart : public IPart {
public:
  void doThat();
};

class ABarThing : public IThing {
  virtual ABarPart* part() {...}
};    

これにより、

AFooThing* pFooThing = ...;
pFooThing->Part()->doThis();

ABarThing pBarThing = ...;
pBarThing->Part()->doThat();

それ以外の

static_cast< AFooPart >(pFooThing->Part())->doThis();

static_cast< ABarPart >(pBarThing->Part())->doThat();

このようなコードに出くわすと、元の設計とより良い設計があるかどうかについて議論することができますが、私の経験では、優先順位、コスト/利益などの制約があり、広範な設計の美化を妨げ、そのような小さなステップしか許可しません。これとして。

于 2009-08-19T15:14:25.627 に答える
0

もう 1 つの例は、抽象クラスではなく具象クラスへのポインタを返す具象ファクトリです (ファクトリが複合オブジェクトを構築する必要があるときに、ファクトリで内部使用するために使用しました)。

于 2009-08-11T14:46:23.487 に答える
0

コンクリート ファクトリを使用してコンクリート製品を生成するシナリオで役立ちます。十分に一般的な、最も専門的なインターフェイスが常に必要です...

具象ファクトリを使用するコードは、製品が具象製品であると安全に想定できるため、抽象クラスに関して具象製品クラスが提供する拡張機能を安全に使用できます。これは確かに構文糖と見なすことができますが、とにかく甘いです。

于 2009-08-11T15:08:06.073 に答える