Molto spesso si è portati a pensare che C#, il linguaggio "di punta" del framework .NET, sia un C++ "migliore", un "C++++" come suggerisce l'immagine, in realtà le differenze sono profonde, più che da un punto di vista sintattico, da un punto di vista concettuale.
Vediamo di seguito una tabella sulle maggiori differenze riguardo i due linguaggi.
C++ | C# |
I riferimenti ad un’altra classe in un altro file del progetto richiedono un #include | Nessun #include è richiesto |
I riferimenti ad una classe in una libreria dinamica (dll) richiedono di includere un file e il link ad una libreria statica (lib) | Non ci sono più le librerie statiche. Si possono aggiungere i riferimenti alle dll (assemblies) direttamente da progetto |
I simboli esportati da una libreria devono essere dichiarati come declspec o in un .DEF file | Basta dichiarare i simboli come pubblici nella libreria. I simboli privati non vengono esportati. |
Se ci si riferisce ad una classe/struttura prima che venga dichiarata, bisogna fare una forward declaration | Non esistono le forward declarations e l’ordine di dichiarazione non è importante |
Passando tra stringhe Unicode/16-bit si richiede:
| Unicode è il formato nativo |
NULL è un concetto, non fa parte del linguaggio. È tipicamente definito come 0 e quindi è l'equivalente di 0. | null è una parola chiave. Non è equivalente a 0. |
I tipi di dato base (int, float, char, ecc) non possono accettare un valore "NULL". Solo i puntatori possono avere un valore "NULL". | Tutti i tipi C # possono accettare null come valore • T, con il supporto null è “T?”, ad es. int con il supporto null è “int?” |
Nessun supporto per “foreach” | Supporto di foreach per iterare su una collezione |
Supporto per try e catch. Nessun supporto per "finally” | |
Nessuna notifica su operazioni di overflow. Per esempio: unsigned x = UINT_MAX; x++; // x == 0 nessuna exception lanciata | |
Nessun supporto nativo per sezioni critiche o multithreading | |
All’interno di uno switch, il break è opzionale | Il break dentro lo switch è obbligatorio |
Nessuna documentazione built-in. Esite la possibilità di usare tools esterni come doxygen | Documentazione XML con “///”. Riconosciuto da IntelliSense in Visual Studio |
Non si può suddividere la definizione di una classe su più file | Le classi parziali permettono di suddividere la definizione di una classe su più source files. |
Nessun supporto per mix di librerie di versioni differenti | Si può fare un mix di librerie di versioni differenti |
Lo sviluppatore deve gestire la memoria direttamente (new/delete) | Supporto di Garbage collection |
Si ha maggiore controllo sulla distruzione degli oggetti, possibilità di memory leaks se non ben gestita | Minore controllo sulla distruzione degli oggetti, ma più sicurezza a causa della gestione automatica del GC |
Gli oggetti “reference” possono essere allocati sia nell’heap che nello stack della memoria dichiarandoli come locali o con una new | Gli oggetti “reference” vengono sempre allocati nell’heap |
Supporto per classi o metodi “friend” | Non esiste il concetto di friend, ma questo è stato in qualche modo sostituito dalle Proprietà di una classe |
I tipi non condividono un tipo root comune | Tutti i tipi derivano dalla classe “Object” |
Pensandoci bene e leggendo vari articoli sull’argomento, ci si rende conto che in realtà la differenza tra un linguaggio come C++ e uno come C# (o Java), è ben più profonda e parte dalla base stessa di come è stato creato il linguaggio.
Il C++ si potrebbe definire come un linguaggio a due livelli di astrazione:
- Livello 1 (low level): gestione diretta della memoria e dell’allocazione delle risorse attraverso i puntatori . A questo livello viene definita la classe come detentrice delle risorse che verranno rilasciate nel suo distruttore. L’acquisizione-inizializzazione-distruzione delle risorse usate dalla classe è completamente a carico del programmatore che deve essere quindi sufficientemente esperto per progetti di medie-grosse dimensioni.
- Livello 2 (high level): a questo livello si vedono solo gli “oggetti” e si possono usare le risorse solo attraverso i metodi esposti dagli “oggetti”. Non esistono puntatori e nemmeno “delete” o “free”: al limite esistono puntatori “intelligenti” detti smart_pointer che si auto gestiscono e distruggono. Si richiede un grado di esperienza del programmatore anche inferiore perché la gestione “delicata” delle risorse è fatta al livello sottostante. Questo livello è molto simile a quello offerto da C#/Java.
Le origini del C++ non partono esclusivamente dal C, di cui comunque si è scelto di mantenere la compatibilità, ma soprattutto da un’idea di Stroustrup che il linguaggio stesso potesse essere esteso, partendo da un nucleo decisamente ridotto all'osso. Il programmatore può decidere di costruire sul linguaggio nativo estensioni difficilmente distinguibili dal linguaggio stesso. Un chiaro esempio di ciò è la libreria STL.
Il C#, come il Java, lasciano meno possibilità e libertà al programmatore, che probabilmente però ne guadagna in “sicurezza”e velocità di apprendimento. C# come Java partono da un livello di astrazione superiore al C++ e però se da un lato ciò comporta l’evitare tutte quelle problematiche che possono insorgere quando si programma a basso livello, dall’altro ne perde in flessibilità e potenza espressiva. C’è da dire che probabilmente la scelta di Java/C# è oculata perchè il target dei linguaggi è molto differente: nel caso di Java/C# si hanno linguaggi destinati ad applicazioni che devono interagire pesantemente col mondo della rete, del web, dei database, delle applicazioni distribuite ecc., per cui si è scelto di semplificare la vita del programmatore dando un livello di astrazione maggiore: in questo modo è stato possibile costruire e far apprendere in tempi relativamente rapidi ambienti quali .NET o Java (inteso come ambiente). Il C++ è stato pensato invece per andare a coprire target quali applicazioni time critical o sistemi embedded: target per cui è conveniente poter agire a differenti livelli di astrazione. Dire che C# è un C++ "migliorato" non è corretto, è più corretto dire che C# è stato pensato per scopi differenti, usare l'uno o l'altro dipende da ciò che dobbiamo costruire.