Quelles sont les différences entre les types «génériques» en C ++ et Java?

Réponses:

144

Il y a une grande différence entre eux. En C ++, vous n'avez pas besoin de spécifier une classe ou une interface pour le type générique. C'est pourquoi vous pouvez créer des fonctions et des classes vraiment génériques, avec la mise en garde d'un typage plus lâche.

template <typename T> T sum(T a, T b) { return a + b; }

La méthode ci-dessus ajoute deux objets du même type et peut être utilisée pour tout type T pour lequel l'opérateur "+" est disponible.

En Java, vous devez spécifier un type si vous souhaitez appeler des méthodes sur les objets passés, quelque chose comme:

<T extends Something> T sum(T a, T b) { return a.add ( b ); }

En C ++, les fonctions / classes génériques ne peuvent être définies que dans les en-têtes, car le compilateur génère différentes fonctions pour différents types (avec lesquels il est appelé). La compilation est donc plus lente. En Java, la compilation n'a pas de pénalité majeure, mais Java utilise une technique appelée "effacement" où le type générique est effacé au moment de l'exécution, donc au moment de l'exécution, Java appelle en fait ...

Something sum(Something a, Something b) { return a.add ( b ); }

La programmation générique en Java n'est donc pas vraiment utile, ce n'est qu'un peu de sucre syntaxique pour aider avec la nouvelle construction foreach.

EDIT: l'avis ci-dessus sur l'utilité a été rédigé par un moi plus jeune. Les génériques de Java aident bien sûr à la sécurité de type.

Alexandru Nedelcu
la source
27
Il a parfaitement raison de dire qu'il ne s'agit que d'un sucre syntaxique élaboré.
alphazero
31
Ce n'est pas du sucre purement syntaxique. Le compilateur utilise ces informations pour vérifier les types. Même si les informations ne sont pas disponibles au moment de l'exécution, je n'appellerais pas quelque chose que la compilation utilise simplement "sucre syntaxique". Si vous appelez cela ainsi, eh bien, C n'est que du sucre syntaxique pour l'assemblage, et ce n'est que du sucre syntaxique pour le code machine :)
dtech
42
Je pense que le sucre syntaxique est utile.
poitroae le
5
Vous avez manqué un point de différence majeur, ce que vous pouvez utiliser pour instancier un générique. En C ++, il est possible d'utiliser le template <int N> et d'obtenir un résultat différent pour tout nombre utilisé pour l'instancier. Il est utilisé pour le méta-programme de compilation. Comme la réponse dans: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal
2
Vous n'êtes pas obligé de «spécifier un type», sous la forme de l'un extendsou l' autre super. La réponse est incorrecte,
Marquis de Lorne
124

Les génériques Java sont très différents des modèles C ++.

Fondamentalement, les modèles C ++ sont essentiellement un ensemble préprocesseur / macro glorifié ( Remarque: puisque certaines personnes semblent incapables de comprendre une analogie, je ne dis pas que le traitement des modèles est une macro). En Java, ils sont essentiellement du sucre syntaxique pour minimiser la conversion standard des objets. Voici une introduction assez décente aux modèles C ++ par rapport aux génériques Java .

Pour élaborer sur ce point: lorsque vous utilisez un modèle C ++, vous créez essentiellement une autre copie du code, comme si vous utilisiez une #definemacro. Cela vous permet de faire des choses comme avoir des intparamètres dans les définitions de modèle qui déterminent la taille des tableaux, etc.

Java ne fonctionne pas comme ça. En Java, tous les objets s'étendent à partir de java.lang.Object donc, pré-génériques, vous écririez du code comme celui-ci:

public class PhoneNumbers {
  private Map phoneNumbers = new HashMap();

  public String getPhoneNumber(String name) {
    return (String)phoneNumbers.get(name);
  }

  ...
}

car tous les types de collection Java utilisaient Object comme type de base afin que vous puissiez y mettre n'importe quoi. Java 5 roule et ajoute des génériques pour que vous puissiez faire des choses comme:

public class PhoneNumbers {
  private Map<String, String> phoneNumbers = new HashMap<String, String>();

  public String getPhoneNumber(String name) {
    return phoneNumbers.get(name);
  }

  ...
}

Et c'est tout ce que sont les génériques Java: des wrappers pour la diffusion d'objets. C'est parce que les génériques Java ne sont pas raffinés. Ils utilisent l'effacement de caractères. Cette décision a été prise parce que Java Generics est arrivé si tard dans la pièce qu'ils ne voulaient pas interrompre la rétrocompatibilité (a Map<String, String>est utilisable chaque fois que a Mapest demandé). Comparez cela à .Net / C # où l'effacement de type n'est pas utilisé, ce qui conduit à toutes sortes de différences (par exemple, vous pouvez utiliser des types primitifs et IEnumerableet IEnumerable<T>n'avoir aucun rapport entre eux).

Et une classe utilisant des génériques compilée avec un compilateur Java 5+ est utilisable sur JDK 1.4 (en supposant qu'elle n'utilise aucune autre fonctionnalité ou classe nécessitant Java 5+).

C'est pourquoi les génériques Java sont appelés sucre syntaxique .

Mais cette décision sur la façon de faire des génériques a des effets tellement profonds que la (superbe) FAQ sur les génériques Java a vu le jour pour répondre aux nombreuses questions que les gens se posent sur les génériques Java.

Les modèles C ++ ont un certain nombre de fonctionnalités que Java Generics n'a pas:

  • Utilisation d'arguments de type primitif.

    Par exemple:

    template<class T, int i>
    class Matrix {
      int T[i][i];
      ...
    }

    Java n'autorise pas l'utilisation d'arguments de type primitif dans les génériques.

  • Utilisation d' arguments de type par défaut , qui est une fonctionnalité qui me manque dans Java, mais il y a des raisons de compatibilité ascendante à cela;

  • Java permet la délimitation des arguments.

Par exemple:

public class ObservableList<T extends List> {
  ...
}

Il faut vraiment souligner que les invocations de modèles avec des arguments différents sont vraiment de types différents. Ils ne partagent même pas les membres statiques. En Java, ce n'est pas le cas.

Outre les différences avec les génériques, pour être complet, voici une comparaison de base de C ++ et Java (et une autre ).

Et je peux également suggérer Penser en Java . En tant que programmeur C ++, beaucoup de concepts comme les objets seront déjà une seconde nature, mais il y a des différences subtiles, il peut donc être intéressant d'avoir un texte d'introduction même si vous en parcourez des parties.

Une grande partie de ce que vous apprendrez en apprenant Java, ce sont toutes les bibliothèques (à la fois standard - ce qui vient dans le JDK - et non standard, qui comprend des éléments couramment utilisés comme Spring). La syntaxe Java est plus verbeuse que la syntaxe C ++ et n'a pas beaucoup de fonctionnalités C ++ (par exemple, la surcharge d'opérateurs, l'héritage multiple, le mécanisme de destruction, etc.) mais cela n'en fait pas non plus strictement un sous-ensemble de C ++.

cletus
la source
1
Ils ne sont pas équivalents dans leur concept. Le meilleur exemple étant le modèle de modèle curieusement récurrent. Le deuxième meilleur est la conception axée sur les politiques. Le troisième meilleur étant le fait que C ++ permet de passer des nombres entiers entre les chevrons (myArray <5>).
Max Lybbert
1
Non, leur concept n'est pas équivalent. Il y a un certain chevauchement dans le concept, mais pas beaucoup. Les deux vous permettent de créer List <T>, mais c'est à peu près tout. Les modèles C ++ vont beaucoup plus loin.
jalf
5
Il est important de noter que le problème d'effacement de type signifie plus que la simple compatibilité avec les versions antérieures Map map = new HashMap<String, String>. Cela signifie que vous pouvez déployer un nouveau code sur une ancienne JVM et qu'il fonctionnera en raison des similitudes du bytecode.
Yuval Adam le
1
Vous noterez que j'ai dit "fondamentalement un préprocesseur / macro glorifié". C'était une analogie parce que chaque déclaration de modèle créera plus de code (par opposition à Java / C #).
cletus
4
Le code du modèle est très différent du copier-coller. Si vous pensez en termes d'expansion macro, tôt ou tard, vous serez frappé par des bugs subtils comme celui-ci: womble.decadentplace.org.uk/c++
Nemanja Trifunovic
86

C ++ a des modèles. Java a des génériques, qui ressemblent un peu aux modèles C ++, mais ils sont très, très différents.

Les modèles fonctionnent, comme leur nom l'indique, en fournissant au compilateur un modèle (attendez-le ...) qu'il peut utiliser pour générer du code de type sécurisé en remplissant les paramètres du modèle.

Les génériques, tels que je les comprends, fonctionnent à l'inverse: les paramètres de type sont utilisés par le compilateur pour vérifier que le code qui les utilise est de type sécurisé, mais le code résultant est généré sans aucun type.

Considérez les modèles C ++ comme un très bon système de macros et les génériques Java comme un outil pour générer automatiquement des typecasts.

 

Shog9
la source
4
C'est une assez bonne explication concise. Un ajustement que je serais tenté de faire est que les génériques Java sont un outil pour générer automatiquement des typecasts qui sont garantis sûrs (sous certaines conditions). D'une certaine manière, ils sont liés au C ++ const. Un objet en C ++ ne sera pas modifié via un constpointeur à moins que const-ness ne soit casté. De même, les transtypages implicites créés par des types génériques en Java sont garantis "sûrs" à moins que les paramètres de type ne soient manuellement rejetés quelque part dans le code.
Laurence Gonsalves
16

Une autre fonctionnalité des modèles C ++ que les génériques Java n'ont pas est la spécialisation. Cela vous permet d'avoir une implémentation différente pour des types spécifiques. Ainsi, vous pouvez, par exemple, avoir une version hautement optimisée pour un int , tout en ayant toujours une version générique pour le reste des types. Ou vous pouvez avoir différentes versions pour les types pointeur et non pointeur. Ceci est pratique si vous souhaitez opérer sur l'objet déréférencé lorsque vous recevez un pointeur.

KeithB
la source
1
La spécialisation des modèles +1 est incroyablement importante pour la métaprogrammation au moment de la compilation - cette différence en elle-même rend les génériques java beaucoup moins puissants
Faisal Vali
13

Il y a une bonne explication de ce sujet dans les génériques Java et les collections par Maurice Naftalin, Philip Wadler. Je recommande vivement ce livre. Citer:

Les génériques en Java ressemblent aux modèles en C ++. ... La syntaxe est volontairement similaire et la sémantique est volontairement différente. ... Sémantiquement, les génériques Java sont définis par effacement, alors que les modèles C ++ sont définis par expansion.

Veuillez lire l'explication complète ici .

texte alternatif
(source: oreilly.com )

Julien Chastang
la source
5

Fondamentalement, les modèles AFAIK et C ++ créent une copie du code pour chaque type, tandis que les génériques Java utilisent exactement le même code.

Oui, vous pouvez dire que le modèle C ++ est équivalent au concept générique Java (bien que plus correctement serait de dire que les génériques Java sont équivalents au concept C ++)

Si vous êtes familier avec le mécanisme de template de C ++, vous pourriez penser que les génériques sont similaires, mais la similitude est superficielle. Les génériques ne génèrent pas de nouvelle classe pour chaque spécialisation et ne permettent pas la «métaprogrammation de modèles».

à partir de: Java Generics

OscarRyz
la source
3

Les génériques Java (et C #) semblent être un simple mécanisme de substitution de type à l'exécution.
Les modèles C ++ sont une construction au moment de la compilation qui vous permet de modifier le langage en fonction de vos besoins. Il s'agit en fait d'un langage purement fonctionnel que le compilateur exécute lors d'une compilation.

Ferruccio
la source
3

Un autre avantage des modèles C ++ est la spécialisation.

template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Special sum(const Special& a, const Special& b) { return a.plus(b); }

Maintenant, si vous appelez sum avec des pointeurs, la deuxième méthode sera appelée, si vous appelez sum avec des objets non pointeurs, la première méthode sera appelée, et si vous appelez sumavec des Specialobjets, la troisième sera appelée. Je ne pense pas que cela soit possible avec Java.

KeithB
la source
2
Peut-être parce que Java n'a pas de pointeurs .. !! pouvez-vous expliquer avec un meilleur exemple?
Bhavuk Mathur
2

Je vais le résumer en une seule phrase: les modèles créent de nouveaux types, les génériques restreignent les types existants.

Marquis de Lorne
la source
2
Votre explication est si brève! Et cela est parfaitement logique pour les personnes qui comprennent bien le sujet. Mais pour les gens qui ne le comprennent pas encore, cela n'aide pas beaucoup. (Quel est le cas de quiconque pose une question sur SO, compris?)
Jakub
1

@Keith:

Ce code est en fait faux et mis à part les petits problèmes ( templateomis, la syntaxe de spécialisation a un aspect différent), la spécialisation partielle ne fonctionne pas sur les modèles de fonction, uniquement sur les modèles de classe. Le code fonctionnerait cependant sans spécialisation partielle du modèle, en utilisant plutôt une ancienne surcharge:

template <typename T> T sum(T a, T b) { return a + b; }
template <typename T> T sum(T* a, T* b) { return (*a) + (*b); }
Konrad Rudolph
la source
2
Pourquoi est-ce une réponse et non un commentaire?
Laurence Gonsalves
3
@Laurence: pour une fois, car il a été posté bien avant l'implémentation des commentaires sur Stack Overflow. D'autre part, car ce n'est pas seulement un commentaire - c'est aussi une réponse à la question: quelque chose comme le code ci-dessus n'est pas possible en Java.
Konrad Rudolph
1

La réponse ci-dessous est tirée du livre Cracking The Coding Interview Solutions au chapitre 13, ce que je trouve très bien.

L'implémentation des génériques Java est enracinée dans une idée de "l'effacement de type:" Cette technique élimine les types paramétrés lorsque le code source est traduit en bytecode Java Virtual Machine (JVM). Par exemple, supposons que vous ayez le code Java ci-dessous:

Vector<String> vector = new Vector<String>();
vector.add(new String("hello"));
String str = vector.get(0);

Lors de la compilation, ce code est réécrit dans:

Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String) vector.get(0);

L'utilisation des génériques Java n'a pas vraiment changé grand-chose à nos capacités; ça rendait les choses un peu plus jolies. Pour cette raison, les génériques Java sont parfois appelés "sucre syntaxique: '.

C'est assez différent du C ++. En C ++, les modèles sont essentiellement un ensemble de macros glorifié, le compilateur créant une nouvelle copie du code du modèle pour chaque type. La preuve en est qu'une instance de MyClass ne partagera pas une variable statique avecMyClass. Cependant, les instances de remorquage de MyClass partageront une variable statique.

/*** MyClass.h ***/
 template<class T> class MyClass {
 public:
 static int val;
 MyClass(int v) { val v;}
 };
 /*** MyClass.cpp ***/
 template<typename T>
 int MyClass<T>::bar;

 template class MyClass<Foo>;
 template class MyClass<Bar>;

 /*** main.cpp ***/
 MyClass<Foo> * fool
 MyClass<Foo> * foo2
 MyClass<Bar> * barl
 MyClass<Bar> * bar2

 new MyClass<Foo>(10);
 new MyClass<Foo>(15);
 new MyClass<Bar>(20);
 new MyClass<Bar>(35);
 int fl fool->val; // will equal 15
 int f2 foo2->val; // will equal 15
 int bl barl->val; // will equal 35
 int b2 bar2->val; // will equal 35

En Java, les variables statiques sont partagées entre les instances de MyClass, quels que soient les di érents paramètres de type.

Les génériques Java et les modèles C ++ présentent un certain nombre d'autres différences. Ceux-ci inclus:

  • Les modèles C ++ peuvent utiliser des types primitifs, comme int. Java ne peut pas et doit à la place utiliser Integer.
  • En Java, vous pouvez limiter les paramètres de type du modèle à un certain type. Par exemple, vous pouvez utiliser des génériques pour implémenter un CardDeck et spécifier que le paramètre type doit s'étendre à partir de CardGame.
  • En C ++, le paramètre type peut être instancié, alors que Java ne le prend pas en charge.
  • En Java, le paramètre type (c'est-à-dire, le Foo dans MyClass) ne peut pas être utilisé pour les méthodes et variables statiques, car celles-ci seraient partagées entre MyClass et MyClass. En C ++, ces classes sont di érentes, le paramètre type peut donc être utilisé pour les méthodes et variables statiques.
  • En Java, toutes les instances de MyClass, quels que soient leurs paramètres de type, sont du même type. Les paramètres de type sont effacés lors de l'exécution. En C ++, les instances avec différents paramètres de type sont de types di érents.
Jaycee
la source
0

Les modèles ne sont rien d'autre qu'un système de macros. Sucre de syntaxe. Ils sont entièrement développés avant la compilation réelle (ou, du moins, les compilateurs se comportent comme si c'était le cas).

Exemple:

Disons que nous voulons deux fonctions. Une fonction prend deux séquences (liste, tableaux, vecteurs, tout ce qui se passe) de nombres et renvoie leur produit interne. Une autre fonction prend une longueur, génère deux séquences de cette longueur, les transmet à la première fonction et renvoie son résultat. Le hic, c'est que nous pourrions faire une erreur dans la deuxième fonction, de sorte que ces deux fonctions ne sont pas vraiment de la même longueur. Nous avons besoin du compilateur pour nous avertir dans ce cas. Pas lorsque le programme est en cours d'exécution, mais lors de sa compilation.

En Java, vous pouvez faire quelque chose comme ceci:

import java.io.*;
interface ScalarProduct<A> {
    public Integer scalarProduct(A second);
}
class Nil implements ScalarProduct<Nil>{
    Nil(){}
    public Integer scalarProduct(Nil second) {
        return 0;
    }
}
class Cons<A implements ScalarProduct<A>> implements ScalarProduct<Cons<A>>{
    public Integer value;
    public A tail;
    Cons(Integer _value, A _tail) {
        value = _value;
        tail = _tail;
    }
    public Integer scalarProduct(Cons<A> second){
        return value * second.value + tail.scalarProduct(second.tail);
    }
}
class _Test{
    public static Integer main(Integer n){
        return _main(n, 0, new Nil(), new Nil());
    }
    public static <A implements ScalarProduct<A>> 
      Integer _main(Integer n, Integer i, A first, A second){
        if (n == 0) {
            return first.scalarProduct(second);
        } else {
            return _main(n-1, i+1, 
                         new Cons<A>(2*i+1,first), new Cons<A>(i*i, second));
            //the following line won't compile, it produces an error:
            //return _main(n-1, i+1, first, new Cons<A>(i*i, second));
        }
    }
}
public class Test{
    public static void main(String [] args){
        System.out.print("Enter a number: ");
        try {
            BufferedReader is = 
              new BufferedReader(new InputStreamReader(System.in));
            String line = is.readLine();
            Integer val = Integer.parseInt(line);
            System.out.println(_Test.main(val));
        } catch (NumberFormatException ex) {
            System.err.println("Not a valid number");
        } catch (IOException e) {
            System.err.println("Unexpected IO ERROR");
        }
    }
}

En C #, vous pouvez écrire presque la même chose. Essayez de le réécrire en C ++, et il ne se compilera pas, vous plaignant de l'expansion infinie des modèles.

MigMit
la source
D'accord, c'est 3 ans mais je réponds quand même. Je ne vois pas votre point. La raison pour laquelle Java génère une erreur pour cette ligne commentée est que vous appelleriez une fonction qui attend deux A avec des arguments différents (A et Cons <A>) et cela est vraiment basique et se produit également lorsqu'aucun générique n'est impliqué. C ++ le fait aussi. A part ça, ce code m'a donné le cancer parce que c'est vraiment horrible. Cependant, vous le feriez toujours comme ça en C ++, vous devez bien sûr apporter des modifications parce que C ++ n'est pas Java, mais ce n'est pas un désavantage des modèles de C ++.
clocktown
@clocktown non, vous NE POUVEZ PAS faire ça en C ++. Aucune quantité de modifications ne permettrait cela. Et c'est un inconvénient des modèles C ++.
MigMit
Ce que votre code était censé faire - avertir de la longueur différente - ne le fait pas. Dans votre exemple commenté, cela ne produit que des erreurs en raison d'arguments non correspondants. Cela fonctionne également en C ++. Vous pouvez taper du code qui est sémantiquement équivalent et bien meilleur que ce désordre en C ++ et en Java.
clocktown
Cela fait. Les arguments ne correspondent pas exactement car les longueurs sont différentes. Vous ne pouvez pas faire cela en C ++.
MigMit
0

Je voudrais citer askanydifference ici:

La principale différence entre C ++ et Java réside dans leur dépendance à la plate-forme. Alors que C ++ est un langage dépendant de la plateforme, Java est un langage indépendant de la plateforme.

L'instruction ci-dessus est la raison pour laquelle C ++ est capable de fournir de vrais types génériques. Bien que Java ait une vérification stricte et par conséquent, ils n'autorisent pas l'utilisation de génériques comme le permet C ++.

Piyush-Ask Any Difference
la source