19

JavaやC#のジェネリックとは異なるC++のテンプレートの側面を理解しています。C#は具体化であり、Javaは型消去を使用し、C ++はダックタイピングを使用します。C++テンプレートでJavaやC#ジェネリックではできないことがいくつかあります(テンプレートの特殊化など)。しかし、JavaジェネリックがC#やC ++でできないことはたくさんあり(たとえば、のようなジェネリックファミリーの制限付き型パラメーターを作成するclass Foo<T extends Comparable<?>>)、 C#ジェネリックでできることはJavaやC ++ではできないこともあります(例:ランタイムジェネリックリフレクション)。[編集:どうやらJavaジェネリックは私が思っていたよりもはるかに弱いです。(これは何かを言っています。)いずれにせよ、それらの無能さにもかかわらず、それらは依然としてC#のジェネリックと一緒にジェネリックと見なされます。]

私が理解していないのは、テンプレートがジェネリックとは概念的に異なる理由です。C ++テンプレートのどの部分が、テンプレートではないが汎用的なものでは実行できないものですか?たとえば、テンプレートをサポートする言語を実装する場合、絶対に何が必要になるでしょうか。言語がジェネリックスをサポートするために必要なものを除外できますか?

私の推測では、テンプレートはジェネリックのスーパーセットであるか、ジェネリックを実装する方法ですが、真のテンプレートと真のジェネリックを区別するものが何であるかはよくわかりません。

4

6 に答える 6

9

うーん..C++テンプレートを深く理解していると言って、ジェネリックとそれらの違いを見て/感じないと言ったら、まあ、おそらくあなたは正しいでしょう:)

ジェネリックがテンプレートよりも優れている方法/理由を説明する多くの違いがあり、多くの違いをリストするなどですが、それはアイデアの核心とはほとんど無関係です。

アイデアは、より良いコードの再利用を可能にすることです。テンプレート/ジェネリックスは、実際の型のいくつかを抽象化する、ある種の高階クラス定義を構築する方法を提供します。

この用語では、それらの間に違いはなく、唯一の違いは、基盤となる言語とランタイムの特定の機能と制約によって強制されるものです。

ジェネリックスはいくつかの追加機能を提供すると主張する人もいるかもしれませんが(通常、オブジェクトのクラスツリーの動的なイントロスペクションについて話す場合)、C++のテンプレートに手動で実装できないものはほとんどありません。ある程度の努力を払えば、それらのほとんどは実装またはエミュレートできるため、「適切なジェネリック」と「実際のテンプレート」の区別としては適切ではありません。

他の人は、C++のコピーアンドペースト動作のおかげで利用できる最適化の潜在的な力が違いだと主張するでしょう。申し訳ありませんが、真実ではありません。JavaとC#のJITもそれを実行できますが、ほとんど実行できますが、非常にうまく実行できます。

ただし、Java / C#のジェネリックをC++のテンプレート機能の真のサブセットにすることができることが1つあります。そして、あなたもそれについて言及しました!

テンプレートの特殊化です。

C ++では、各特殊化は完全に異なる定義として動作します。

C ++では、template<typename T> FooT==intに特化したものは次のようになります。

class Foo<int> 
{
    void hug_me();

    int hugs_count() const;
}

T==MyNumericTypeに特化した「同じ」テンプレートは次のようになります。

class Foo<MyNumericType> 
{
    void hug_me();

    MyNumericType get_value() const;
    void  reset_value() const;
}

参考までに:これは単なる擬似コードであり、コンパイルされません:)

JavaとC#のジェネリックはどちらもそれを行うことができません。なぜなら、それらの定義では、すべてのジェネリック型の実体化が同じ「ユーザーインターフェイス」を持つことを示しているからです。

さらに、C++はSFINAEルールを使用します。テンプレートには、多くの「理論的に衝突する」スペシャライゼーションの定義が存在する可能性があります。ただし、テンプレートを使用する場合は、「実際に良い」ものだけを使用します。

上記の例と同様のクラスで、次を使用する場合:

 Foo<double> foood;
 foood.reset_value();

... "reset_value"が欠落しているため、最初の特殊化はコンパイルされないため、2番目の特殊化のみが使用されます。

ジェネリックでは、それを行うことはできません。可能なすべてのメソッドを持つジェネリッククラスを作成する必要があります。そうすると、実行時に内部オブジェクトが動的に検査され、使用できないメソッドに対して「実装されていない」または「サポートされていない」例外がスローされます。それは...ただひどいです。このようなことは、コンパイル時に可能になるはずです。

テンプレートの特殊化とSFINAEの実際の能力、影響、問題、および全体的な複雑さは、ジェネリックとテンプレートを真に差別化するものです。単純に、ジェネリックスは、特殊化が不可能であり、したがってSFINAEが不可能であるような方法で定義されます。したがって、メカニズム全体が逆説的にはるかに簡単/単純になります。

コンパイラーの内部に実装するのが簡単/簡単であり、非賢明な頭脳によって理解されることの両方。

Java / C#のジェネリックの全体的な利点には同意しますが、特殊化、インターフェイスの柔軟性、およびSFINAEルールが本当に恋しいです。ただし、正気のオブジェクト指向デザインに関連する重要なことを1つ言及しなければ、公平ではありません。タイプxxxのテンプレートの特殊化によって、クライアントAPIが実際に変更される場合は、おそらく別の名前を付けて、別のテンプレートを形成する必要があります。 。テンプレートで実行できるすべての追加機能は、ほとんどの場合、ツールセットに追加されました。これは、C ++では反映がなく、何らかの方法でエミュレートする必要があったためです。SFINAEは、コンパイル時のリフレクションの形式です。

したがって、違いの世界で最大のプレーヤーは、ランタイムの欠陥を隠すために適用されるホットフィックスの奇妙な(有益な)副作用に減少します。これは、ランタイムの内省がほぼ完全に欠如していることです:))

したがって、言語によって強制されるいくつかの任意のもの、またはランタイムプラットフォームによって強制されるいくつかの任意のもの以外に違いはないと言います。

それらはすべて高階クラスまたは関数/メソッドの形式であり、これが最も重要な機能であると思います。

于 2013-03-26T11:45:27.253 に答える
4

まず、RTTI/イントロスペクションがほとんどの回答の大部分を占めることは興味深いと思います。まあ、それはジェネリックスとテンプレートの違いではなく、イントロスペクションのある言語とそれを持たない言語の違いです。それ以外の場合は、Javaクラスを使用したC++クラスとJava関数を使用したC++関数の違いであると主張することもできます...

イントロスペクションを邪魔にならないようにすると、主な違いは、テンプレートがチューリング完全言語を定義し、プログラムできる恐ろしい文法を備えていても、スタイルが機能することです。私が聞いた最初の本当に複雑な例(コードが欲しいのですが、そうではありません)は、コンパイル時に素数を計算するプログラムでした。これは別の違いをもたらします。テンプレートは、型引数、テンプレート引数、または非型引数を取ることができます(非型とは、値など、型またはテンプレートではないものを指しintます)。

これは他の回答でも言及されていますが、テンプレートを特殊化でき、SFINAEがあると言っても、これら2つの機能がチューリング完全言語を生成するのに十分であるとは明確に述べていません。

于 2013-03-26T13:38:05.857 に答える
3

JavaジェネリックがC#やC ++でできないことはたくさんあります(たとえば、のようなジェネリックのファミリーの有界型パラメーターを作成するclass Foo<T extends Comparable<?>>

その例では完全には当てはまりません。

template <typename Comparable>
struct Foo {
    static bool compare(const Comparable &lhs, const Comparable &rhs) {
        return lhs == rhs;
    }
};

このクラステンプレートはcompare、テンプレートパラメータが等式比較可能なタイプである場合にのみ、関数を正常にインスタンス化します。これは「有界型パラメーター」とは呼ばれませんが、同じ目的を果たします。

ComparableC ++で、ダックタイプの概念ではなく、明示的なインターフェイス(つまり、基本クラス)として扱いたい場合はstatic_assert(is_base_of<Comparable, T>::value, "objects not Comparable");、または何でもできます。

于 2013-03-26T11:29:20.307 に答える
2

いいえ、テンプレートはジェネリックのスーパーセットではありません。C++テンプレートでは、C#ジェネリックと同じレベルのランタイムサポートがありません。つまり、C ++のRTTIは、Reflectionのようにテンプレートのメタデータを検出して提供できません。 C#のジェネリック。

これに加えて、私はこのスニペットが好きです:

C ++テンプレートは、コンパイル時モデルを使用します。テンプレートをC++プログラムで使用すると、洗練されたマクロプロセッサが使用されたかのようになります。

C#ジェネリックは、コンパイラーの機能であるだけでなく、ランタイムの機能でもあります。Listなどのジェネリック型は、コンパイル後もジェネリック性(ジェネリック性)を維持します。または、別の見方をすれば、C ++コンパイラがコンパイル時に行う置換は、C#ジェネリックの世界ではJIT時に行われます。

完全な記事については、こちらを参照してください:C#ジェネリックはC ++テンプレートとどのように比較されますか?

于 2013-03-26T10:45:57.203 に答える
0

これは古いスレッドであり、私の担当者は受け入れられた回答にコメントするには低すぎますが、私は追加したいと思いました:

明示的な特殊化に加えて、C ++テンプレートとC#ジェネリックのもう1つの重要な違いは、C++で使用される非型テンプレートパラメーターです。

template<int bar> class Foo {};

Foo<1> a;
Foo<2> b;

a = b; //error, different types. 

非型テンプレートパラメーターは、任意の整数型、列挙型、およびコンパイル時に決定できるポインター(静的ストレージ変数と関数ポインター)にすることができます。C ++ 20では、特定の制限付きでクラスタイプにすることもできます。

C#もJavaジェネリックもそれを行うことはできません。

型以外のパラメータに明示的に特化することもできます。

ちなみに、Dプログラミング言語では、ジェネリックプログラミングの命名法として「テンプレート」という用語を使用しており、その機能は、少なくとも私にとってはC#/JavaよりもC++に精神的に一致しているように感じます。

非型パラメーターがC#から除外された技術的な理由はわかりませんが、私が使用する言語として、最近では他の言語よりもこの機能を見逃していることがあります。

于 2019-01-04T04:55:00.013 に答える
-1

私の答えは、C++テンプレートとJavaジェネリックに限定します。

  1. C ++テンプレート(クラステンプレートと関数テンプレート)はコンパイル時のポリモーフィズムを実装するためのメカニズムですが、AFAIKJavaジェネリックは実行時のメカニズムです。
  2. C ++テンプレートを使用すると、ジェネリックプログラミングを行うことができ、実際には完全に別個のプログラミングスタイル/パラダイムですが、Javaジェネリックはそれ自体がOOスタイルです。下記参照:
  3. C ++テンプレートはダックタイピングに基づいていますが、Javaジェネリックは型消去に基づいています。C ++では、、、vector<int>およびvector<Shape *>vector<double>4 vector<vector<Matrix>>つの異なるタイプですが、JavaCell<int>では、Cell<Integer> Cell<double>およびCell<Matrix>は同じタイプです。より正確には、コード生成中にコンパイラーは最初に型を消去します。次の論文のコードで確認できます:VladimirBatov。JavaGenericsとC++テンプレート。C / C ++ユーザージャーナル、2004年7月。

    public class Cell<E>
    {
       private E elem;
       public Cell(E elem)
       { 
          this.elem = elem;
       }
       public E GetElement()
       { 
          return elem;
       }
       public void SetElement(E elem)
       { 
          this.elem = elem;
       } 
    }
    
    boolean test()
    { 
      Cell<Integer> IntCell(10); 
      Cell<String> StrCell(“Hello”);
      return IntCell.getClass() ==
             StrCell.getClass(); // returns true
    }
    

つまり、Javaはジェネリックのふりをしますが、C++は実際にはジェネリックです。

于 2013-03-26T12:08:35.177 に答える