Question

Java a des génériques et C++ fournit un modèle de programmation très solide avec templates.Alors, quelle est la différence entre les génériques C++ et Java ?

Était-ce utile?

La solution

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 véritablement génériques, avec la mise en garde d'un typage plus souple.

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 disposant de l'opérateur "+".

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 invoqué).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, c'est seulement un peu de sucre syntaxique pour aider avec la nouvelle construction foreach.

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

Autres conseils

Les génériques Java sont massivement différent des modèles C++.

Fondamentalement, en C++, les modèles sont essentiellement un ensemble de préprocesseurs/macros glorifiés (Note: 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 passe-partout des objets.Voici un assez bon introduction aux modèles C++ et aux génériques Java.

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

Java ne fonctionne pas comme ça.En Java, tous les objets s'étendent de java.lang.Object donc, avant les génériques, vous écririez du code comme ceci :

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

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

  ...
}

parce que tous les types de collections Java utilisaient Object comme type de base afin que vous puissiez y mettre n'importe quoi.Java 5 est déployé et ajoute des génériques afin 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 :wrappers pour lancer des objets.C'est parce que les génériques Java ne sont pas raffinés.Ils utilisent l'effacement de type.Cette décision a été prise parce que Java Generics est arrivé si tard dans la pièce qu'ils ne voulaient pas rompre la compatibilité ascendante (un Map<String, String> est utilisable chaque fois qu'un Map est demandé).Comparez cela à .Net/C# où l'effacement de type n'est pas utilisé, ce qui conduit à toutes sortes de différences (par ex.vous pouvez utiliser des types primitifs et IEnumerable et IEnumerable<T> n'ont aucun rapport les uns avec les autres).

Et une classe utilisant des génériques compilés 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 manière de faire des génériques a des effets profonds à tel point que le (superbe) FAQ sur les génériques Java a surgi pour répondre aux très nombreuses questions que les gens se posent sur les génériques Java.

Les modèles C++ possèdent un certain nombre de fonctionnalités que les génériques Java n'ont 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 de arguments de type par défaut, qui est une fonctionnalité qui me manque en Java, mais il y a des raisons de compatibilité ascendante à cela ;

  • Java permet de limiter les 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 en réalité de types différents.Ils ne partagent même pas de membres statiques.En Java, ce n'est pas le cas.

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

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

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

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...) 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 dans l'autre sens :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 bien système de macros et les génériques Java comme outil de génération automatique de transtypages.

 

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, disposer d'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.Cela s'avère pratique si vous souhaitez opérer sur l'objet déréférencé lorsque vous lui donnez un pointeur.

Il y a une excellente explication de ce sujet dans Génériques et collections JavaPar Maurice Naftalin, Philip Wadler.Je recommande vivement ce livre.Citer:

Les génériques en Java ressemblent à des modèles en C ++....La syntaxe est délibérément similaire et la sémantique est délibérément différente....Sémantiquement, les génériques Java sont définis par l'effacement, où les modèles C ++ sont définis par l'expansion.

Veuillez lire l'explication complète ici.

alt text
(source: oreilly.com)

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

Oui toi peut dire ce modèle C++ est équivalent au générique Java concept (bien qu'il serait plus correct de dire que les génériques Java sont équivalents au C++ dans leur concept)

Si vous êtes familier avec le mécanisme de modèle 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 non plus de « métaprogrammation de modèles ».

depuis: Génériques Java

Les génériques Java (et C#) semblent être un simple mécanisme de substitution de type au moment de 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.

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 sum sum avec Special objets, le troisième sera appelé.Je ne pense pas que cela soit possible avec Java.

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.

@Keith :

Ce code est en fait faux et mis à part les petits problèmes (template omis, la syntaxe de spécialisation est différente), spécialisation partielle n'a pas travaillez sur des modèles de fonctions, uniquement sur des modèles de classe.Le code fonctionnerait cependant sans spécialisation partielle du modèle, mais en utilisant plutôt une simple 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); }

La réponse ci-dessous est tirée du livre Décrypter l'entretien de codage Solutions au chapitre 13, que je trouve très bonnes.

L'implémentation des génériques Java est ancrée dans une idée d'« effacement de type » : cette technique élimine les types paramétrés lorsque le code source est traduit en bytecode de la machine virtuelle Java (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 de génériques Java n’a pas vraiment changé nos capacités ;ça rendait juste 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 de variable statique avec MyClass.Cependant, deux instances 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 diffé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 plutôt utiliser entier.
  • En Java, vous pouvez restreindre les paramètres de type du modèle pour être d'un certain type.Par exemple, vous pouvez utiliser des génériques pour implémenter un carddeck et spécifier que le paramètre de type doit s'étendre à partir de CardGame.
  • En C ++, le paramètre de type peut être instancié, tandis que Java ne prend pas en charge cela.
  • En Java, le paramètre de 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 diffé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 au moment de l'exécution.En C++, les instances avec des paramètres de type différents sont des types différents.

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 proprement dite (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 problème est que nous pourrions faire une erreur dans la deuxième fonction, de sorte que ces deux fonctions n'auront pas vraiment la même longueur.Nous avons besoin que le compilateur nous prévienne dans ce cas.Pas lorsque le programme est en cours d'exécution, mais lors de la 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 sera pas compilé, se plaignant de l'expansion infinie des modèles.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top