web-dev-qa-db-ja.com

C ++がC#やJavaなどのSTLコンテナーの共分散をサポートしないのはなぜですか?

共分散および反分散機能 はC#およびJavaコレクションで十分にサポートされています。しかし、C++ではSTLコンテナーでそれらをサポートしていません。なぜそうなのですか?

たとえば、次のコードはC#およびJavaでコンパイルされますが、C++ではコンパイルされません(ただし、構文は特定の言語に翻訳する必要があります)。

class Base
{

};

class Child : public Base
{

};

int main()
{
    std::vector<Base*> baseArray;
    std::vector<Child*> ChildArray;

    baseArray = ChildArray;

    return 0;
}
2
Sisir

その理由は、基になるオブジェクトとメモリモデルです。

推論を簡略化するには:

  • JavaおよびC#では、クラスのオブジェクトは 参照によって管理されます です。コンテナはオブジェクトの値を直接格納するのではなく、値の検索場所を示す参照を格納します。したがって、技術的には、同じコンテナ内で異なるタイプのオブジェクトを混合する(ポリモーフィズム)、または共変タイプのオブジェクトにコンテナを使用するのが簡単です。唯一の制約は言語のセマンティクスです。これにより、共変コンテナの実装が大幅に容易になります。

  • C++では、オブジェクトは値によって管理され、そのメモリモデルのルールに従います。基本的には、特定のタイプのオブジェクトを固定サイズ内に格納する必要があります(もちろん、動的サイズの要素へのポインターを含めることができます)。したがって、コンテナはコンパイル時にそのオブジェクトのタイプを知っている必要があります。残念ながら(そうでない場合でも)、C++では個別のコンパイルも可能です。そのため、1つの変換単位でAnimalsのコンテナーをコンパイルすると、コンパイラーはCatのサイズを知らない可能性があります(まだ開発されていない可能性もあります)。このため、言語に共分散を実装することは非常に困難です。

興味深いことに、C#では、値で管理されるオブジェクト(構造体の場合)を使用できます。しかし、これは Microsoftのドキュメント および this SO question で説明されているように、分散は参照型にのみ適用されます。

もちろん、これらはすべて単純化された説明であり、言語弁護士はいくつかの詳細について議論することができますが、それがアイデアを理解するのに役立つことを願っています。

9
Christophe

C++テンプレートは不変です。つまり、共分散または反変をサポートしていません。

したがって、STLコンテナが共変ではないのは、C++がそれをサポートしていないためです。

ご了承ください std::vectorは可変なので、とにかく共変にすることはできません。不変である必要があります。そうでなければ、タイプセーフではありません。

4
Jörg W Mittag

Javaでは、ジェネリックスは不変です。共分散または反変を実現するには、境界ワイルドカードを使用する必要があります。 Javaで必要なことを行うには、次のように宣言する必要があります。

List<? extends Base> baseArray;
List<Child> childArray;
baseArray = ChildArray;

? extendsワイルドカードを使用することにより、Javaは、タイプList<? extends Base>の参照を使用してnull以外の要素をリストに追加できないようにします。ワイルドカードは不明なタイプを表し、追加しようとしているものがそのタイプのインスタンスであるかどうかはわかりません。エレメントから取得できるのは要素のみです。

(Javaの配列は共変ですが、ここでは配列ではなく、一般的なコンテナーについて話しているようです。)

C++には制限付きのワイルドカードはありません。 Javaでのバインドされたワイルドカードの最も一般的な使用例は、メソッドが読み取る必要があるだけのコレクションパラメータを受け入れる場合です。そのため、正確な型引数は重要ではありません。 type引数が特定のタイプまたはそのサブタイプであること:

void printListOfBase(List<? extends Base> list) {
    // you can call methods of Base on the elements of list
}

C++では、C++テンプレートのインスタンス化は「ダックタイプ」であるため、テンプレートを使用しても同じことをバインドなしで実現できます。 Javaとは異なり、ジェネリッククラスまたはジェネリックメソッドは1回だけコンパイルされ、クラス/メソッドをコンパイルするときにコンパイラーに、実行しているものが型セーフであることを証明する必要があります。C++では、テンプレート化されたクラスまたは関数がコンパイルされます。各インスタンス化(つまり、使用される各タイプ引数)ごとに個別に指定できるため、コンパイラーは、特定のインスタンス化をコンパイルするときに、事前に境界を指定する必要なく、タイプが機能するかどうかをチェックできます。

void printListOfBase<T>(std::vector<T> list) {
    // you can call methods of Base on the elements of list
    // and it will compile as long as T has such a method
}

ワイルドカードでパラメーター化された型のローカル変数を使用する特定のケースについては、これはあまり一般的ではなく、C++で直接対応するものはありません。

1
user102008

C#とJavaでは、x = y;1、同じObjectに(少なくとも)2つの名前が付けられます。 C++ではまだ2つの異なるオブジェクトがありますが、一部のコードが実行され、おそらく現在同じ値を持っています。

C++でのbaseArray = ChildArrayのセマンティクスは、許可されている場合は非常に異なり、コピー操作になります。

あなたcanChildArrayの内容をbaseArrayにコピーしますが、=はコピーしません

baseArray.assign(ChildArray.begin(), ChildArray.end());

boostライブラリには、このような状況のためのヘルパーがあります

#include <boost/range/iterator_range.hpp>

baseArray = boost::copy_range<decltype(baseArray)>(ChildArray);

脚注1:xyはプリミティブではないと仮定します。その場合、C++の場合と一致します。

1
Caleth