MokaByte Numero 02 - Novembre 1996
 Java e C++ 
seconda parte
di
Fabrizio Giudici 
 

 



Dopo aver esaminato le caratteristiche più generiche del linguaggio Java, in questo articolo parleremo finalmente di programmazione ad oggetti. Vedremo quali sono i paradigmi di programmazione supportati, che differenze ci sono rispetto al C++... ed ancora una volta come Java semplifichi la vita al programmatore in modo sostanziale, anche se le soluzioni che adotta non piacciono a tutti...


Da buon linguaggio orientato agli oggetti, Java supporta tutti i costrutti più importanti, come information hiding ("mascheramento" dei componenti interni di un oggetto per ottenere una buon separazione tra interfaccia ed implementazione), ereditarietà (capacità di creare un nuovo tipo di oggetto prendendo come base un tipo pre-esistente), polimorfismo (capacità di definire diversi comportamenti di risposta relativi a stimoli esterni, a seconda del tipo di ciascun oggetto). Tutto esattamente come il C++... almeno a prima vista. Incominciamo dall'information hiding. In C++ si possono specificare tre diverse modalità di accesso ai membri di una classe:
private:per i membri che devono essere visibili solo all'interno della classe;
protected: per i membri che possono essere visibili anche a sottoclassi, ma non all'esterno;
public:per i membri che possono essere visibili sempre.
In C++ i membri non public possono essere resi accessibili a classi qualsiasi se esse vengono dichiarate friend della classe che li contiene. La friendship di classi ha sempre suscitato polemiche, un po' come per il goto nei linguaggi strutturati. C'è chi dice che non serve ed anzi è dannosa, mentre altri la difendono. Apparentemente la friendship è una violazione al principio dell'information hiding, perché è un modo per "forzare" le regole che ci si è imposte. Questo è vero se se ne fa un abuso, ma considerate questo esempio (in C++) in cui una classe Collection può contenere un insieme di interi:

class Collection {

        private: int a[20]; 

        public: inline void addElement (int x) { 

                        /* aggiunge un elemento */ }



};


Se non si vuole rivelare come viene implementata (con un array come nell'esempio oppure con una lista linkata) è possibile evitare di usare metodi per l'accesso diretto agli elementi contenuti, ma usare una seconda classe specializzata, detta enumeratore o iteratore:

class Enumerator { 

        private: Collection &c;

        public: inline Enumerator (Collection &cc) { 

                c = cc 

        }; 

        inline int next(){ 

                /* accede agli elementi di Collection */ 

        } 

        inline int end() { 

                /* restituisce 1 quando si arriva in fondo */ 

        } 

}; 

                ... 

Collection c; 

for (Enumerator e(c);!e.end(); ) 

        cout << e.next() << "\n";

Il ciclo for accede serialmente a tutti gli elementi di c in modo completamente indipendente dall'implementazione di Collection. Il problema è evidente: Collection deve garantire solo ad Enumerator l'accesso ai suoi membri, ma Enumerator non è una sottoclasse di Collection. L'uso della friendship è quindi necessario, però il programmatore deve assicurarsi di usarla correttamente, senza abusarne per "mettere delle pezze" a problemi derivanti da cattiva progettazione.

I modi di accesso in Java sono simili al C++, con qualche piccola, ma significativa, differenza. I modi private e public conservano il loro significato, mentre protected vuol dire una cosa leggermente diversa. Esiste poi un ulteriore modo, che può essere chiamato package friend, che permette l'accesso a tutte e sole le classi che appartengono allo stesso package, anche se non sono direttamente sottoclassi. Questo limita un po' i danni della friendship: è logico attendersi che le classi dentro uno stesso package abbiano delle affinità tra di loro per cui concedere loro l'accesso ad alcuni membri non dovrebbe essere troppo pericoloso. Curiosamente non esiste nessuna parola chiave per questo modo, che viene assunto per default se non viene esplicitamente specificato nient'altro (questa è un po' una contraddizione con i principi generali di chiara leggibilità dei sorgenti: tutto sommato una parola chiave package friend non avrebbe guastato...). Chi legge documentazione obsoleta potrà trovare accenno ad un modo chiamato "private protected" che però è stato eliminato dalla versione più recente del linguaggio.

Ereditarietà multipla

Vediamo ora il secondo problema: l'ereditarietà multipla. Supponiamo di avere una classe A da cui deriviamo B1 e B2 ed una quarta classe C che eredita sia da B1 che da B2, come in figura:

class A { int a; };                             [ a ]

class B1 : public A { int b1; };                [ a | b1 ]      

class B2 : public A { int b2; };                [ a | b2 ]      

class  C : public B1, public B2 { int c; };     [ a | b1 | a | b2 ]



Quante variabili a sono contenute in C? Ricordando come viene definito il layout in memoria delle classi C++ e la risposta è immediata: due (si veda ancora la figura). Ora è vero che un puntatore a C è anche un valido puntatore sia a B1 che ad A, ma non lo è per B2! Per ottenere un valido puntatore a B2 bisogna aggiungere un offset per "saltare" la prima variabile a e b1. Un moderno compilatore C++ è in grado di accorgersene... ma se facciamo maldestramente un po' di aritmetica di puntatori per conto nostro facilmente commetteremo errori disastrosi. E non basta! Avere due copie della variabile a in C può andare bene o no a seconda di quello che stiamo facendo. Usando la cosiddetta ereditarietà virtuale possiamo eliminare la seconda copia:
class A { int a; };                                     [ a ]           

class B1 : public A { int b1; };                        [ a | b1 ]      

class B2 : public A { int b2; };                        [ a | b2 ]      

class  C : public B1, virtual public B2 { int c; };     [ a | b1 | b2 ]



Però adesso non è più neanche possibile ottenere un valido puntatore a B2! La situazione cambia a seconda dell'ordine di ereditarietà e di quale classe è ereditata con virtual public. Insomma, una situazione piuttosto complessa che va tenuta sotto controllo con molta attenzione. Anche per risolvere questi problemi Java è molto drastico: semplicemente viene proibita qualsiasi forma di ereditarietà multipla! Però l'ereditarietà multipla certamente aiuta a modellare meglio il mondo reale. Pensiamo per esempio alla definizione di un metodo per stampare un oggetto: in C++ possiamo dichiarare una classe base Printable come segue:
class Printable { 

        virtual void print(); 

}

Per qualsiasi classe Derived derivata da una classe Base possiamo fare così:
class Derived : public Base, virtual public Printable { ... };

ed il gioco è fatto. Java non permette ereditarietà multipla, ma fortunatamente offre un'alternativa: le interfacce (intefaces). Il concetto è il seguente: la proprietà di essere stampabile è considerata troppo generica perché abbia senso legare più classi con relazioni di ereditarietà, pertanto ci limitiamo ad indicare semplicemente che esse possiedono tutte un metodo con lo stesso prototipo (e probabilmente stessa semantica). Ecco come fare:
interface Printable { 

        void print(); 

} 



class Derived extends Base implements Printable { 

        void print() {...} 

} 



class Derived2 extends Base2 implements Printable { 

        void print() {...} 

}

void DoSomething (Printable pr) { 

        pr.print()

}

Printable si limita a dichiarare il metodo print(), ma non specifica alcun codice per esso. Notate la clausola implements per segnalare che Derived implementa tutti i metodi di Printable. Come mostrato dal metodo DoSomething(), è possibile eseguire il metodo print() per qualsiasi classe che implementi Printable, ma tutte queste classi non necessitano di essere imparentate tra di loro. Questo meccanismo può apparire molto simile a definire una classe astratta Printable in C++, ma la realtà è molto diversa (anche se in alcune circostanze con ereditarietà multipla di classi astratte si può fare effettivamente qualcosa di simile). La differenza fondamentale è che Printable proprio non esiste come oggetto: non possiamo definire variabili membro né specificare codice per i suoi metodi. Stiamo semplicemente dicendo che sia Derived e Derived2 possiedono un metodo con lo stesso nome, poi ci penserà Java, durante l'esecuzione del programma, a cercare l'indirizzo del metodo giusto, volta per volta. La differenza più profonda è che le classi che implementano le interfacce devono esplicitamente definire il codice per i metodi implementati. Questo può voler dire scrivere qualche riga di codice in più, minimizzabile delegando ad una classe di appoggio esterna l'implementazione:
interface Printable { 

        void print(); 

} 



class PrintableObject implements Printable { 

        void print() {...} 

} 



class Derived extends Base implements Printable { 

        PrintableObject pobj = new PrintableObject();

        void print() { 

                pobj->print(); 

        } 

} 



class Derived2 extends Base2 implements Printable { 

        PrintableObject pobj = new PrintableObject(); 

        void print() { 

                pobj->print(); 

        } 

}

Le interfacce esistono in Objective C (un C con oggetti, molto legato alla piattaforma Next ed al suo sistema operativo NextStep, di cui ha condiviso la poca fortuna) e sono state proposte per il nuovo ANSI/ISO C++. Lo GNU C le implementa, per il momento come estensione allo standard attuale, con il nome di signatures.

Gestione degli oggetti: niente più puntatori

In C++ esistono ben tre modi diversi per riferirsi ad un oggetto: per valore, per puntatore e per reference

class Object { 

        void doSomething(); {...} 

};

Object value; 

Object *pointer = new Object();

Object &reference = value; 

                ... 

value.doSomething(); 

pointer->

doSomething();

reference.doSomething();

In Java ne esiste uno solo: per reference (che ha un significato diverso che in C++):
Object reference = new Object(); 

reference.doSomething();

Come si vede, il reference è una via di mezzo tra il puntatore (richiede allocazione esplicita con new) ed il modo per valore (si usa il punto "." e non la freccia "->"): Da un punto di vista implementativo certamente il reference è di fatto un puntatore, perché contiene l'indirizzo dell'oggetto a cui "punta"; ma, a differenza dei puntatori tradizionali, non è possibile conoscere tale indirizzo. In questo modo è completamente abolita qualsiasi forma di aritmetica dei puntatori, una delle caratteristiche più "pericolose" del C/C++. Questa "protezione" da parte di Java è addirittura estesa al livello del codice macchina. Se si va a leggere la documentazione della Java Virtual Machine si scopre che tutte le istruzioni sono fortemente typed, cioè conoscono il tipo esatto degli operandi con cui hanno a che fare. Non esistono assolutamente modi di convertire gli indirizzi espressi dai reference in interi, neanche con "bassi trucchi di programmazione" direttamente in assembler (come per esempio fare un push di un intero ed un pop di un reference): il code verifier, che è parte integrante della Virtual Machine, compie controlli in tal senso al momento del caricamento di un programma e abortisce la sua esecuzione se riscontra sequenze di istruzioni non legali. Possono esistere reference nulli, che cioè non puntano ad alcun oggetto (tutti i reference sono inizializzati al valore nullo per default), proprio come i puntatori nulli del C/C++; ma le stesse istruzioni della Virtual Machine compiono sempre un controllo su ogni reference prima di usarlo ed in caso di tentativo di dereferenziazione di reference nulli generano un'eccezione.

Garbage Collection

Un altro meccanismo relativo alla gestione degli oggetti che Java ci mette a disposizione è la garbage collection. In pratica, il runtime di Java è in grado di rendersi conto automaticamente quando un oggetto non serve più ed in tal caso provvede a distruggerlo. Per questo motivo in Java non esistono distruttori né operatore delete, ma possiamo dire che in un certo senso è come se essi venissero generati ed usati automaticamente senza che il programmatore se ne renda conto. Come funziona la garbage collection? Ci sono molti diversi algoritmi per questo meccanismo, ma in generale possiamo dire che il fondamento di tutto è il seguente approccio, detto mark and release: ad intervalli prefissati di tempo viene eseguito del codice che esamina prima di tutto le variabili statiche del programma, cercando eventuali puntatori ad oggetti. Ogni volta che si trova un oggetto si marca (mark) come allocata la memoria che usa, poi si procede ricorsivamente ispezionando tutti i puntatori che l'oggetto stesso contiene. Alla fine di questa fase si è giunti per forza a marcare tutti e soli i blocchi di memoria allocati ancora usati; siccome tutti gli altri non sono referenziati da nessuno, per definizione non servono più a niente. Quindi si passa alla seconda fase, il rilascio effettivo (release) dei blocchi di memoria non referenziati.

In Java tutto questo lavoro viene svolto da un processo in background che viene "risvegliato" ad intervalli regolari di tempo, oppure quando c'è necessità di liberare la memoria allocata. La garbage collection può apparire un processo dispendioso e pesante al punto tale di penalizzare la performance del programma. Essa è già stata utilizzata in linguaggi come i vecchi gloriosi Basic interpretati o in linguaggi moderni come lo Smalltalk ed il Lisp, che non sono certamente esempi di velocità di esecuzione (l'autore ha ricordi personali molto indicativi al riguardo), soprattutto se paragonati al C/C++. Generalmente la scelta di usare o non usare la garbage collection viene presa in conseguenza di modi di pensiero assolutamente complementari: tanto per fare un esempio, esiste una vecchia battuta che dice:

"I programmatori di Lisp sanno che la gestione della memoria è così importante che non può essere lasciata agli utenti, mentre i programmatori di C (e C++, nota personale) sanno che la gestione della memoria è così importante che non può essere lasciata al sistema.".

Cioè: le opinioni sulla garbage collection sono spesso frutto di "ideologie". I vantaggi della garbage collection sono numerosi e evidenti: non essendo più necessario occuparsi della gestione della memoria ci si può completamente dedicare al problema da risolvere. La gestione della memoria spesso è una responsabilità molto complessa: ci si deve affidare a schemi di progettazione in cui volta per volta deve essere sempre chiaro chi è che "possiede" gli oggetti e quindi ha il "diritto/dovere" di distruggerli. Non è il caso di entrare in dettagli, ma questo approccio quasi sempre porta a situazioni molto intricate se si ha a che fare con applicazioni complesse. Gli svantaggi della garbage collection sono genericamente una riduzione della performance: parte della potenza di calcolo viene distolta dall'elaborazione vera e propria, a volte possono verificarsi brevi interruzioni dei programmi quando il garbage collector entra in azione e possono verificarsi dei problemi nel rilascio di risorse. Per esempio, se la chiusura di un file o di una finestra vengono delegate completamente al garbage collector, a causa del suo funzionamento asincrono può capitare che esse "rimangano in agonia" per qualche tempo. Non è certo bello premere un pulsante per chiudere una finestra e vedersela sullo schermo ancora per un minuto... Come rispondere a queste critiche? Be', le risorse come file o finestre dovrebbero essere comunque gestite manualmente, visto che il momento della loro chiusura generalmente può essere definito senza troppi problemi, ed il garbage collector non ci impedisce assolutamente di farlo. Per quanto riguarda lo scadimento di performance, negli ultimi anni sono stati sviluppati algoritmi molto più raffinati che riducono sensibilmente questo problema. Esistono addirittura algoritmi con capacità di tempo reale che garantiscono l'assenza di pause durante l'esecuzione entro limiti prefissati (chi ha qualche interesse in questo campo troverà dei riferimenti bibliografici in fondo all'articolo). Java non pone alcuna restrizione in riguardo all'implementazione effettiva del suo garbage collector, così sarà in grado di tenersi aggiornato con gli sviluppi del settore. Alcune società che sviluppano software hanno messo in commercio librerie di garbage collection perfettamente compatibili con linguaggi come il C e il C++, e forse questo è un segno che la mentalità potrebbe cambiare in futuro.

La comunità C++ ha rifiutato l'uso di garbage collector per il prossimo ANSI/ISO C++, argomentando, con Bjarne Stroustrup in testa, che ciò andrebbe contro la filosofia del linguaggio. Questo è certamente vero, perché il C++ è sempre stato un linguaggio flessibile che ha sempre permesso al programmatore di scegliere le proprie strategie; e non è molto difficile implementare un garbage collector in C++. Rimane da discutere se questa "delega di responsabilità" è un bene o un male. Certo è che la filosofia di Java in proposito è completamente all'opposto: la garbage collection, unita alla politica di implementazione dei reference, porta alla notevole conseguenza per cui in Java non è più possibile avere puntatori appesi (dangling pointers)! Infatti o un reference è nullo (e in caso di uso improprio Java se ne accorge) oppure non può fare altro che puntare ad un oggetto valido. Secondo molte statistiche i puntatori appesi sono la causa principale di problemi durante lo sviluppo ed il debugging dei programmi.

Le librerie standard avranno più probabilità di essere tali, in quanto Sun ha considerato il problema parallelamente alla nascita del linguaggio, mentre per il C++ si sta arrivando solo adesso alla definizione della Standard Template Library (STL), dopo che la proliferazione è già avvenuta.

Eliminazione dell'overloading degli operatori

In Java non è semplicemente possibile effettuare l'overloading degli operatori. Secondo i creatori del linguaggio, questo è un costrutto che può rendere i programmi poco chiari (ricordiamoci che Java elimina tutto ciò che può essere pericoloso). Questo concetto può essere chiarificato dal seguente esempio in C++:

class BrokenComplex { 

        private: float re, im;

        public: inline float abs() { 

                return sqrt(re * re + im * im); 

        } 

        inline int operator < (BrokenComplex c) { 

                return abs() < c.abs(); 

        } 

        inline void operator = (BrokenComplex c) { 

                re = c.re; im = c.im; } 

        };

Sebbene non ci siano errori di sintassi e tutto il codice possa essere compilato correttamente, la classe BrokenComplex è decisamente stata mal progettata. L'operatore "minore di" è stato implementato magari perché il programmatore ha necessità di memorizzare in modo univoco una serie di numeri in una lista, ma è concettualmente sbagliato: i numeri complessi non hanno relazione d'ordine. Anche l'operatore di assegnamento contiene un errore più sottile: è vero che è in grado di ricopiare un numero complesso in un altro, ma la sua dichiarazione non rispetta la semantica degli operatori di assegnazione del C++. Per definizione gli operatori di assegnazione in C/C++ ritornano un valore, che è quello dell'oggetto assegnato. In caso contrario non sarà possibile effettuare assegnazioni multiple del tipo a = b = c. Tirando le somme, il C++ consente di ridefinire gli operatori senza verificare che ne venga rispettata la semantica corretta (cosa che d'altronde è praticamente impossibile fare automaticamente). Al posto degli operatori si usano consueti metodi dal nome alfanumerico. Per esempio, per verificare l'uguaglianza tra due oggetti a e b anziché scrivere a == b si usa a.equals(b). C'è un altro aspetto molto sottile in tutto questo ragionamento: di fatto a == b è corretto in Java, ma equivale a verificare se due differenti reference puntano allo stesso oggetto. La stessa sintassi non può quindi essere usata per un confronto tra oggetti differenti, perché le due operazioni sono ben diverse. In C++ non c'è questo problema: se a e b fossero due oggetti specificati by value, cioè dichiarati come
MyObject a, b;
allora chiaramente a == b sarebbe un confronto tra oggetti diversi. Se fossero invece puntatori
MyObject *a, *b;
allora a == b sarebbe il confronto tra riferimenti allo stesso oggetto, mentre il confronto tra oggetti diversi sarebbe *a == *b. Tutto questo perché in C++ ci sono diversi modi per riferirsi ad un oggetto. Per questi motivi, quando leggiamo in un programma C++ a == b, siamo obbligati ad andare a vedere come sono stati dichiarati a e b per capire di che operazione si tratta. Java, in omaggio ai criteri di semplicità e chiarezza, elimina queste ambiguità.

Supporto per il multithreading

Nel primo articolo di questa serie ho scritto come il C++ sia una serie di stratificazioni successive, dovute alla necessità di aggiornamento, ma spesso incompatibili tra di loro. Il supporto per il multithreading ne è la migliore (o peggiore...) riprova. Il multithreading è una forma di programmazione concorrente per cui esistono più "flussi di esecuzione" (threads) attivi contemporaneamente condividendo gli spazi di indirizzamento, cioè di fatto avendo accesso alle stesse istanze di variabili. Prima del multithreading esisteva il multitasking, per cui i "flussi di esecuzione" (chiamati in questo caso task o processi) avevano un loro proprio spazio degli indirizzi, pertanto tutte le istanze di variabili erano duplicate. Il C nacque quando il multithreading di fatto non era ancora stato proposto, pertanto le risorse erano non condivise per default. Si potevano creare risorse condivise (per esempio memoria), ma bisognava farlo esplicitamente e pertanto era chiaro che il programmatore dovesse prendersene cura in maniera opportuna. Con il multithreading il C evidenzia i suoi limiti. A parte il fatto che ogni variabile condivisa richiede di essere protetta esplicitamente, una buona parte delle funzioni di libreria standard risulta inutilizzabile in quanto non solo la loro implementazione, ma anche la semantica fa uso di variabili statiche per restituire i risultati. Più thread paralleli possono causare conflitti usando contemporaneamente la stessa variabile statica e non è possibile implementare trasparentemente un metodo di sincronizzazione perché la variabile statica è di fatto accessibile dall'esterno. Il C++ non può modificare questa situazione a causa dei vincoli i compatibilità che lo legano al suo predecessore e si limita a permetterci di definire librerie di classi (per le quali peraltro non esiste alcuno standard universalmente riconosciuto) per facilitare il controllo dei thread. Il modello di multithreading di Java è invece elegante e compatto. Interi metodi o solo sezioni specifiche possono essere semplicemente definite come synchronized e la gestione della sincronizzazione viene effettuata trasparentemente. Tutti i package standard di Java ovviamente sono stati progettati coerentemente e possono essere usati senza alcun problema. La facilità con cui è possibile creare e controllare un thread parallelo in Java ne rendono possibile l'utilizzo senza doversi porre troppi problemi, consentendo di utilizzare modelli di programmazione più potenti.

Velocità di esecuzione

Veniamo al "tallone d'Achille" di Java: la velocità di esecuzione. È chiaro che, in quanto linguaggio interpretato, Java non potrebbe neanche essere paragonato al C++. Infatti, in media, possiamo dire che Java è circa un ordine di grandezza più lento del suo rivale. La situazione migliorerà sensibilmente con tecniche innovative che permetteranno di tradurre "al volo" il codice macchina Java in codice macchina nativo, senza che il programmatore se ne debba preoccupare esplicitamente. La traduzione può avvenire durante la prima esecuzione del codice in questione o addirittura contemporaneamente al suo downloading dalla rete. L'applicazione delle più moderne tecnologie sull'ottimizzazione del codice dovrebbe far riguadagnare una buona parte dello svantaggio... ma non tutto: ci sono limiti alla performance intrinseci nel linguaggio. Basti considerare un paio di esempi: per gli array, l'accesso ai singoli elementi è sempre controllato per quanto riguarda l'indice ed inoltre è anche sincronizzato per permettere il funzionamento in multithreading. Chiaramente l'approccio di sincronizzare sempre, anche quando non serve, rende il linguaggio più sicuro, ma meno efficiente. Come secondo esempio pensiamo al fatto che in C++ si possono definire classi di calcolo, come numeri complessi e vettori, che se ben progettate portano a codice efficiente quasi come se si esplicitassero tutti i calcoli a mano. In Java ogni oggetto di ogni classe deve essere sempre allocato dinamicamente, così l'efficienza va a farsi benedire... Insomma, se vogliamo realizzare un programma che vuole sfruttare al massimo tutta la potenza della macchina, Java non è certamente competitivo con il C++. Però anche questo fa parte della filosofia di Java. Il linguaggio di Sun ci dice esplicitamente di barattare parte della potenza di calcolo con la maggior safety dei programmi realizzati. Se pensiamo a questo concetto con l'approccio di una software house che fa i conti su quanto può guadagnare vendendo i suoi programmi, allora possiamo renderci conto che spesso un programma meno efficiente, ma più robusto ed affidabile, può essere preferibile ad un campione di velocità che però dà una non buona impressione di sé presso i clienti, magari richiedendo lunghe e costose sessioni di debugging aggiuntivo, con la conseguente distribuzione di patch... per non tenere conto delle spese aggiuntive necessarie a portare i programmi su piattaforme diverse.

Conclusione

Con questo articolo si chiude la serie dedicata al confronto tra i linguaggi Java e C++. Dovrebbe essere chiaro a tutti che Java non è attualmente in grado di sostituire il C++ come "linguaggio per ogni stagione", mentre appare molto interessante per tutta quella serie di applicazioni dove la robustezza e la portabilità sono più importanti dell'efficienza. Ribadisco ovviamente che stiamo parlando di linguaggio e non sistema-di-sviluppo, qualsiasi considerazione riguardante il network computing (per il quale Java è una vera e propria rivoluzione) è stata deliberatamente esclusa dal nostro discorso.

Cosa si può dire in conclusione? Vi dirò il mio parere personale. Il C++ soffre di gravi problemi, dovuti al fatto che è di fatto un mezzosangue tra un linguaggio quick and dirty come il C e un linguaggio orientato agli oggetti. Il comitato ANSI/ISO C++ che sta studiando le specifiche del prossimo standard se ne rende perfettamente conto e sta correndo ai ripari e molti problemi verranno risolti con un progressivo allontanamento del C++ dal suo progenitore C; ma non sempre ciò avverrà in maniera elegante. La brutta impressione di "linguaggio con una serie di pezze sovrapposte" rimarrà e secondo me non potrà mai essere eliminata del tutto, anche a causa della necessità di procedere gradualmente mantendendo certe compatibilità con il passato.

Java invece è stato riprogettato da zero tenendo conto organicamente di tutte le esigenze a cui deve rispondere e come conseguenza è molto più raffinato ed elegante del C++. Essendo legato al concetto di network computing, per cui la macchina virtuale è un componente fondamentale, deve però pagare un prezzo alla sua eleganza: l'efficienza. Volete sapere qual'è il mio sogno? Un qualcosa di intermedio tra C++ e Java (che avrebbe potuto chiamarsi J++ se Microsoft non avesse già registrato un trademark su questo nome per il suo ambiente di sviluppo...), per sostituire il C++ in quei casi in cui l'efficienza è uno dei requisiti più importanti, magari con la reintroduzione (limitata) dell'overloading dei puntatori, ovviamente producendo direttamente codice nativo. Non penso di essere l'unico ad avere avuto quest'idea, così potrebbe non essere improbabile vedere qualcosa di simile nascere nel prossimo futuro... Staremo a vedere!
 
 
 
 
 
 
Fabrizio Giudici (fritz@dibe.unige.it) sta svolgendo il Dottorato di Studi in Ingegneria Elettronica ed Informatica presso l’Università di Genova. Opera presso il Networking Competence Center (http://dibe.unige.it/department/ncc) ed il suo interesse di ricerca primario è la sicurezza nell’ambito delle reti di calcolatori. Si interessa anche di programmazione orientata agli oggetti (OOP), con particolare riferimento ai linguaggi C++ e Java.

 

 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it