Aggiungerò la mia voce al rumore e cercherò di chiarire le cose:
I generici C # ti consentono di dichiarare qualcosa di simile.
List<Person> foo = new List<Person>();
e poi il compilatore ti impedirà di inserire cose che non sono Personnell'elenco.
Dietro le quinte il compilatore C # sta solo inserendo List<Person>il file .NET dll, ma in fase di esecuzione il compilatore JIT va e costruisce un nuovo set di codice, come se avessi scritto una classe di elenco speciale solo per contenere persone, qualcosa di simileListOfPerson .
Il vantaggio di questo è che lo rende davvero veloce. Non ci sono casting o altre cose, e poiché la dll contiene le informazioni che si tratta di un elenco Person, altro codice che lo esamina in seguito utilizzando la riflessione può dire che contienePerson oggetti (quindi ottieni intellisense e così via).
Lo svantaggio di questo è che il vecchio codice C # 1.0 e 1.1 (prima che aggiungessero i generici) non comprende questi nuovi List<something>, quindi devi riconvertire manualmente le cose al vecchioList per interagire con loro. Questo non è un grosso problema, perché il codice binario C # 2.0 non è compatibile con le versioni precedenti. L'unica volta che ciò accadrà è se stai aggiornando un vecchio codice C # 1.0 / 1.1 a C # 2.0
Java Generics ti consente di dichiarare qualcosa di simile.
ArrayList<Person> foo = new ArrayList<Person>();
In superficie sembra lo stesso, e più o meno lo è. Il compilatore ti impedirà anche di inserire cose che non sono Personnell'elenco.
La differenza è cosa succede dietro le quinte. A differenza di C #, Java non crea uno speciale ListOfPerson- usa solo il vecchio semplice ArrayListche è sempre stato in Java. Quando togli le cose dallo schieramento, il solito Person p = (Person)foo.get(1);ballo di casting deve ancora essere fatto. Il compilatore ti sta risparmiando le pressioni dei tasti, ma la velocità di hit / casting è ancora sostenuta come sempre.
Quando le persone menzionano "Type Erasure" questo è ciò di cui stanno parlando. Il compilatore inserisce i cast per te, quindi "cancella" il fatto che deve essere un elenco Personnon solo diObject
Il vantaggio di questo approccio è che il vecchio codice che non comprende i generici non deve preoccuparsene. Ha ancora a che fare con lo stesso vecchio ArrayListcome ha sempre fatto. Questo è più importante nel mondo java perché volevano supportare la compilazione di codice utilizzando Java 5 con generics e farlo funzionare su vecchie 1.4 o JVM precedenti, con cui Microsoft ha deliberatamente deciso di non preoccuparsi.
Lo svantaggio è il colpo di velocità che ho citato in precedenza, e anche perché non c'è nessuna ListOfPersonpseudo-classe o qualcosa del genere nei file .class, codice che lo guarda in seguito (con riflessione o se lo estrai da un'altra raccolta dove è stato convertito Objecto così via) non può dire in alcun modo che deve essere un elenco contenente solo Persone non solo qualsiasi altro elenco di array.
I modelli C ++ ti consentono di dichiarare qualcosa di simile
std::list<Person>* foo = new std::list<Person>();
Sembra generici C # e Java, e farà quello che pensi che dovrebbe fare, ma dietro le quinte stanno accadendo cose diverse.
Ha più in comune con i generici di C # in quanto costruisce speciali pseudo-classespiuttosto che buttare via le informazioni sul tipo come fa java, ma è una cosa completamente diversa.
Sia C # che Java producono un output progettato per le macchine virtuali. Se scrivi del codice che contiene una Personclasse, in entrambi i casi alcune informazioni su una Personclasse andranno nel file .dll o .class, e JVM / CLR farà qualcosa con questo.
C ++ produce codice binario x86 grezzo. Tutto è non è un oggetto, e non c'è macchina virtuale sottostante che ha bisogno di sapere di una Personclasse. Non c'è boxing o unboxing e le funzioni non devono appartenere a classi, o addirittura niente.
Per questo motivo, il compilatore C ++ non pone restrizioni su ciò che puoi fare con i modelli: praticamente qualsiasi codice che potresti scrivere manualmente, puoi ottenere modelli da scrivere per te.
L'esempio più ovvio è l'aggiunta di cose:
In C # e Java, il sistema generics deve sapere quali metodi sono disponibili per una classe e deve trasmetterli alla macchina virtuale. L'unico modo per dirlo è codificare la classe effettiva in o utilizzare le interfacce. Per esempio:
string addNames<T>( T first, T second ) { return first.Name() + second.Name(); }
Quel codice non verrà compilato in C # o Java, perché non sa che il tipo Tfornisce effettivamente un metodo chiamato Name (). Devi dirlo - in C # come questo:
interface IHasName{ string Name(); };
string addNames<T>( T first, T second ) where T : IHasName { .... }
E poi devi assicurarti che le cose che passi a addNames implementino l'interfaccia IHasName e così via. La sintassi java è diversa ( <T extends IHasName>), ma soffre degli stessi problemi.
Il caso "classico" di questo problema è cercare di scrivere una funzione che lo faccia
string addNames<T>( T first, T second ) { return first + second; }
Non puoi effettivamente scrivere questo codice perché non ci sono modi per dichiarare un'interfaccia con il +metodo al suo interno. Hai fallito.
Il C ++ non soffre di nessuno di questi problemi. Al compilatore non interessa passare i tipi a nessuna macchina virtuale: se entrambi gli oggetti hanno una funzione .Name (), verrà compilato. Se non lo fanno, non lo faranno. Semplice.
Così il gioco è fatto :-)