Quali sono le differenze tra i tipi "generici" in C ++ e Java?


Risposte:


144

C'è una grande differenza tra loro. In C ++ non è necessario specificare una classe o un'interfaccia per il tipo generico. Ecco perché puoi creare funzioni e classi veramente generiche, con l'avvertenza di una digitazione più libera.

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

Il metodo sopra aggiunge due oggetti dello stesso tipo e può essere utilizzato per qualsiasi tipo T con l'operatore "+" disponibile.

In Java devi specificare un tipo se vuoi chiamare metodi sugli oggetti passati, qualcosa del tipo:

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

In C ++ le funzioni / classi generiche possono essere definite solo nelle intestazioni, poiché il compilatore genera funzioni diverse per tipi diversi (con cui viene invocato). Quindi la compilazione è più lenta. In Java la compilazione non ha una grossa penalità, ma Java utilizza una tecnica chiamata "cancellazione" in cui il tipo generico viene cancellato in fase di esecuzione, quindi in fase di esecuzione Java sta effettivamente chiamando ...

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

Quindi la programmazione generica in Java non è davvero utile, è solo un po 'di zucchero sintattico per aiutare con il nuovo costrutto foreach.

EDIT: l'opinione sopra sull'utilità è stata scritta da un sé più giovane. I generici di Java aiutano ovviamente con la sicurezza dei tipi.


27
Ha perfettamente ragione che è solo uno zucchero sintattico elaborato.
alphazero

31
Non è zucchero puramente sintattico. Il compilatore utilizza queste informazioni per verificare i tipi. Anche se le informazioni non sono disponibili in fase di esecuzione, non definirei qualcosa che il compilato utilizza semplicemente "zucchero sintattico". Se lo chiamassi così, allora C è solo zucchero sintattico per l'assemblaggio, e questo è solo zucchero sintattico per il codice macchina :)
dtech,

42
Penso che lo zucchero sintattico sia utile.
poitroae,

5
Hai perso un grande punto di differenza, quello che puoi usare per creare un'istanza di un generico. In c ++ è possibile usare il template <int N> e ottenere un risultato diverso per qualsiasi numero usato per istanziarlo. Viene utilizzato per il meta progaming in fase di compilazione. Come la risposta in: stackoverflow.com/questions/189172/c-templates-turing-complete
stonemetal

2
Non è necessario "specificare un tipo", sotto forma di extendso super. La risposta è errata,
Marchese di Lorne il

124

Java Generics è notevolmente diverso dai modelli C ++.

Fondamentalmente in C ++ i modelli sono fondamentalmente un preprocessore / macro set glorificato ( Nota: poiché alcune persone sembrano incapaci di comprendere un'analogia, non sto dicendo che l'elaborazione del modello sia una macro). In Java sono fondamentalmente zucchero sintattico per ridurre al minimo la fusione di oggetti in caldaia. Ecco un'introduzione abbastanza decente ai modelli C ++ rispetto ai generici Java .

Per approfondire questo punto: quando usi un modello C ++, fondamentalmente stai creando un'altra copia del codice, proprio come se usassi una #definemacro. Questo ti permette di fare cose come avere intparametri nelle definizioni dei template che determinano le dimensioni degli array e così via.

Java non funziona così. In Java tutti gli oggetti si estendono da java.lang.Object così, pre-generici, dovresti scrivere codice in questo modo:

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

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

  ...
}

perché tutti i tipi di raccolta Java utilizzavano Object come tipo di base in modo da poter inserire qualsiasi cosa in essi. Java 5 gira e aggiunge generici in modo da poter fare cose come:

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

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

  ...
}

E questo è tutto Java Generics: involucri per il casting di oggetti. Questo perché Java Generics non è raffinato. Usano la cancellazione del tipo. Questa decisione è stata presa perché Java Generics è arrivato così tardi nel pezzo che non volevano interrompere la retrocompatibilità (a Map<String, String>è utilizzabile ogni volta che Mapviene richiesto). Confronta questo con .Net / C # dove non viene utilizzata la cancellazione del tipo, il che porta a tutti i tipi di differenze (ad esempio puoi usare tipi primitivi IEnumerablee IEnumerable<T>non avere alcuna relazione l'uno con l'altro).

E una classe che usa generici compilati con un compilatore Java 5+ è utilizzabile su JDK 1.4 (supponendo che non utilizzi altre funzionalità o classi che richiedono Java 5+).

Ecco perché Java Generics è chiamato zucchero sintattico .

Ma questa decisione su come fare i generici ha effetti profondi così tanto che le (superbe) FAQ di Java Generics sono sorte per rispondere alle molte, molte domande che le persone hanno su Java Generics.

I modelli C ++ hanno una serie di funzionalità che Java Generics non ha:

  • Uso di argomenti di tipo primitivo.

    Per esempio:

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

    Java non consente l'uso di argomenti di tipo primitivo in generici.

  • Uso di argomenti di tipo predefiniti , che è una caratteristica che mi manca in Java ma ci sono ragioni di compatibilità all'indietro per questo;

  • Java consente il limite degli argomenti.

Per esempio:

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

È davvero necessario sottolineare che le invocazioni di modelli con argomenti diversi sono in realtà tipi diversi. Non condividono nemmeno i membri statici. In Java non è così.

A parte le differenze con i generici, per completezza, ecco un confronto di base tra C ++ e Java (e un altro ).

E posso anche suggerire di pensare in Java . Come programmatore C ++ molti concetti come oggetti saranno già di seconda natura ma ci sono sottili differenze, quindi può valere la pena avere un testo introduttivo anche se si sfogliano parti.

Molto di ciò che imparerai imparando Java sono tutte le librerie (entrambe standard - ciò che viene fornito nel JDK - e non standard, che include cose comunemente usate come Spring). La sintassi Java è più dettagliata della sintassi C ++ e non ha molte funzionalità C ++ (ad es. Sovraccarico dell'operatore, ereditarietà multipla, meccanismo di distruttore, ecc.) Ma ciò non lo rende nemmeno un sottoinsieme di C ++.


1
Non sono equivalenti nel concetto. L'esempio migliore è il modello di modello curiosamente ricorrente. Il secondo è il design orientato alle politiche. Il terzo è il fatto che C ++ consente il passaggio di numeri interi tra parentesi angolari (myArray <5>).
Max Lybbert,

1
No, non sono equivalenti nel concetto. C'è qualche sovrapposizione nel concetto, ma non molto. Entrambi ti consentono di creare Elenco <T>, ma questo è quanto più lontano possibile. I modelli C ++ vanno molto oltre.
jalf

5
È importante notare che il problema della cancellazione del tipo significa molto più che compatibilità con le versioni precedenti Map map = new HashMap<String, String>. Significa che puoi distribuire un nuovo codice su una vecchia JVM e verrà eseguito a causa delle somiglianze nel bytecode.
Yuval Adam,

1
Noterai che ho detto "fondamentalmente un preprocessore / macro glorificato". Era un'analogia perché ogni dichiarazione del modello creerà più codice (al contrario di Java / C #).
cletus,

4
Il codice modello è molto diverso da copia e incolla. Se pensi in termini di espansione macro, prima o poi sarai colpito da bug sottili come questo: womble.decadentplace.org.uk/c++/…
Nemanja Trifunovic

86

C ++ ha modelli. Java ha generici, che assomigliano in qualche modo ai modelli C ++, ma sono molto, molto diversi.

I modelli funzionano, come suggerisce il nome, fornendo al compilatore un modello (aspetta ...) che può usare per generare un codice sicuro compilando i parametri del modello.

I generici, a quanto ho capito, funzionano al contrario: i parametri di tipo vengono utilizzati dal compilatore per verificare che il codice che li utilizza sia sicuro per i tipi, ma il codice risultante viene generato senza tipi.

Pensa ai modelli C ++ come a un ottimo sistema macro e ai generici Java come uno strumento per generare automaticamente i tipografi.

 


4
Questa è una spiegazione abbastanza buona e concisa. Un accenno che sarei tentato di fare è che i generici Java sono uno strumento per generare automaticamente tipografie che sono garantite come sicure (con alcune condizioni). In qualche modo sono legati al C ++ const. Un oggetto in C ++ non verrà modificato tramite un constpuntatore a meno che il const-ness non venga eliminato. Allo stesso modo, i cast impliciti creati da tipi generici in Java sono garantiti come "sicuri" a meno che i parametri del tipo non vengano eliminati manualmente da qualche parte nel codice.
Laurence Gonsalves

16

Un'altra caratteristica dei modelli C ++ che i generici Java non hanno è la specializzazione. Ciò ti consente di avere un'implementazione diversa per tipi specifici. Quindi, ad esempio, puoi avere una versione altamente ottimizzata per un int , pur avendo una versione generica per il resto dei tipi. Oppure puoi avere versioni diverse per tipi di puntatore e non puntatore. Ciò è utile se si desidera operare sull'oggetto con riferimenti quando si passa un puntatore.


1
La specializzazione dei template +1 è incredibilmente importante per la metaprogrammazione in fase di compilazione - questa differenza in sé rende i generici Java molto meno potenti
Faisal Vali,

13

C'è una grande spiegazione di questo argomento in Java Generics and Collections di Maurice Naftalin, Philip Wadler. Consiglio vivamente questo libro. Per citare:

I generici in Java assomigliano ai modelli in C ++. ... La sintassi è volutamente simile e la semantica è deliberatamente diversa. ... Semanticamente, i generici Java sono definiti dalla cancellazione, mentre i modelli C ++ sono definiti dall'espansione.

Si prega di leggere la spiegazione completa qui .

testo alternativo
(fonte: oreilly.com )


5

Fondamentalmente, i modelli AFAIK, C ++ creano una copia del codice per ogni tipo, mentre i generici Java usano esattamente lo stesso codice.

Sì, puoi dire che il modello C ++ è equivalente al concetto generico Java (anche se più correttamente sarebbe dire che i generici Java sono equivalenti al concetto C ++)

Se hai familiarità con il meccanismo del modello di C ++, potresti pensare che i generici siano simili, ma la somiglianza è superficiale. I generici non generano una nuova classe per ogni specializzazione, né consentono la "metaprogrammazione dei modelli".

da: Java Generics


3

I generici Java (e C #) sembrano essere un semplice meccanismo di sostituzione del tipo di runtime.
I modelli C ++ sono un costrutto in fase di compilazione che ti offre un modo per modificare il linguaggio in base alle tue esigenze. Sono in realtà un linguaggio puramente funzionale che il compilatore esegue durante una compilazione.


3

Un altro vantaggio dei modelli C ++ è la specializzazione.

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); }

Ora, se si chiama sum con puntatori, verrà chiamato il secondo metodo, se si chiama sum con oggetti non puntatore verrà chiamato il primo metodo e se si chiama sumcon Specialoggetti, verrà chiamato il terzo. Non penso che questo sia possibile con Java.


2
Forse perché Java non ha puntatori .. !! puoi spiegare con un esempio migliore?
Bhavuk Mathur,

2

Lo riassumo in una sola frase: i modelli creano nuovi tipi, i generici limitano i tipi esistenti.


2
La tua spiegazione è così breve! E ha perfettamente senso per le persone che comprendono bene l'argomento. Ma per le persone che non lo capiscono ancora, non aiuta molto. (Qual è il caso di chiunque faccia domande su SO, capito?)
Jakub,

1

@Keith:

Tale codice è in realtà errato e, a parte i piccoli problemi ( templateomessa, la sintassi della specializzazione ha un aspetto diverso), la specializzazione parziale non funziona sui modelli di funzione, ma solo sui modelli di classe. Il codice funzionerebbe comunque senza una specializzazione parziale del template, usando invece un vecchio sovraccarico normale:

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

2
Perché questa è una risposta e non un commento?
Laurence Gonsalves

3
@Laurence: per una volta, perché è stato pubblicato molto prima che i commenti fossero implementati su Stack Overflow. Per un altro, perché non è solo un commento, è anche una risposta alla domanda: qualcosa come il codice sopra non è possibile in Java.
Konrad Rudolph

1

La risposta che segue è dal libro Cracking The Coding Interview Solutions al capitolo 13, che penso sia molto buono.

L'implementazione dei generici Java si basa su un'idea di "cancellazione del tipo:" Questa tecnica elimina i tipi parametrizzati quando il codice sorgente viene tradotto nel bytecode JVM (Java Virtual Machine). Ad esempio, supponiamo di avere il codice Java di seguito:

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

Durante la compilazione, questo codice viene riscritto in:

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

L'uso dei generici Java non ha cambiato molto le nostre capacità; ha reso le cose un po 'più belle. Per questo motivo, i generici Java vengono talvolta chiamati "zucchero sintattico:".

Questo è abbastanza diverso dal C ++. In C ++, i modelli sono essenzialmente un set di macro glorificato, con il compilatore che crea una nuova copia del codice modello per ciascun tipo. Ne è una prova il fatto che un'istanza di MyClass non condividerà una variabile statica con MyClass. Le istanze di traino di MyClass, tuttavia, condivideranno una variabile statica.

/*** 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

In Java, le variabili statiche sono condivise tra istanze di MyClass, indipendentemente dai diversi parametri di tipo.

I modelli generici Java e C ++ presentano numerose altre differenze. Questi includono:

  • I modelli C ++ possono usare tipi primitivi, come int. Java non può e deve invece utilizzare Integer.
  • In Java, è possibile limitare i parametri di tipo del modello a un determinato tipo. Ad esempio, è possibile utilizzare generics per implementare CardDeck e specificare che il parametro type deve estendersi da CardGame.
  • In C ++, il parametro type può essere istanziato, mentre Java non lo supporta.
  • In Java, il parametro type (ovvero Foo in MyClass) non può essere utilizzato per metodi e variabili statici, poiché questi sarebbero condivisi tra MyClass e MyClass. In C ++, queste classi sono diverse, quindi il parametro type può essere usato per metodi e variabili statici.
  • In Java, tutte le istanze di MyClass, indipendentemente dai loro parametri di tipo, sono dello stesso tipo. I parametri del tipo vengono cancellati in fase di esecuzione. In C ++, le istanze con parametri di tipo diverso sono tipi diversi.

0

I modelli non sono altro che un sistema macro. Sintassi zucchero. Vengono completamente espansi prima della compilazione effettiva (o, almeno, i compilatori si comportano come se fosse il caso).

Esempio:

Diciamo che vogliamo due funzioni. Una funzione prende due sequenze (elenco, matrici, vettori, qualunque cosa vada) di numeri e restituisce il loro prodotto interno. Un'altra funzione richiede una lunghezza, genera due sequenze di quella lunghezza, le passa alla prima funzione e restituisce il risultato. Il problema è che potremmo commettere un errore nella seconda funzione, in modo che queste due funzioni non siano realmente della stessa lunghezza. Abbiamo bisogno del compilatore per avvisarci in questo caso. Non quando il programma è in esecuzione, ma durante la compilazione.

In Java puoi fare qualcosa del genere:

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");
        }
    }
}

In C # puoi scrivere quasi la stessa cosa. Prova a riscriverlo in C ++ e non verrà compilato, lamentandoti dell'infinita espansione dei modelli.


Okay, ho 3 anni ma rispondo comunque. Non vedo il tuo punto. L'intero motivo per cui Java genera un errore per quella linea commentata è perché si chiamerebbe una funzione che prevede due A con argomenti diversi (A e contro <A>) e questo è veramente di base e si verifica anche quando non sono coinvolti generici. Anche C ++ lo fa. A parte questo, questo codice mi ha dato il cancro perché è davvero orribile. Tuttavia, lo faresti ancora così in C ++, ovviamente devi apportare modifiche perché C ++ non è Java, ma questo non è uno svantaggio dei modelli di C ++.
Clocktown,

@clocktown no, NON puoi farlo in C ++. Nessuna quantità di modifiche lo consentirebbe. E questo è uno svantaggio dei modelli C ++.
MigMit,

Ciò che il tuo codice avrebbe dovuto fare - avvertire di diversa lunghezza - non lo fa. Nel tuo esempio commentato produce solo errori a causa di Argomenti non corrispondenti. Funziona anche in C ++. Puoi digitare un codice semanticamente equivalente e molto meglio di questo pasticcio in C ++ e Java.
Clocktown,

Lo fa. Gli argomenti non corrispondono esattamente perché le lunghezze sono diverse. Non puoi farlo in C ++.
MigMit,

0

Vorrei citare askanydifference qui:

La differenza principale tra C ++ e Java risiede nella loro dipendenza dalla piattaforma. Mentre C ++ è un linguaggio dipendente dalla piattaforma, Java è un linguaggio indipendente dalla piattaforma.

L'istruzione precedente è il motivo per cui C ++ è in grado di fornire veri tipi generici. Mentre Java ha un controllo rigoroso e quindi non consente l'uso di generici come C ++ lo consente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.