MokaByte Numero 03 - Dicembre 1996

 
Il nuovo ISO C++ 
e Java
di
Fabrizio
Giudici
 

 

Il 24 e 25 Settembre scorsi Infomedia ha organizzato, presso il Grand Hotel Ramada di Milano, due interessanti giornate dedicate, rispettivamente, ai linguaggi C++ e Java. Sono stati affrontati argomenti molto disparati, dalle caratteristiche dei linguaggi, compresa un'analisi delle nuove feature del prossimo standard C++, agli strumenti di sviluppo, all'interfacciamento con i database, fino ad arrivare all'analisi di sofisticate tecniche di programmazione. In questo articolo completiamo di fatto il paragone tra C++ e Java iniziato nelle puntate precedenti, dando un'occhiata a quello che sarà il nostro prossimo C++, confrontando le nuove soluzioni proposte con quelle adottate dal linguaggio di Sun.

link articolo oopStandardizzazione

Cominciamo purtroppo con una brutta notizia: i lavori del comitato di standardizzazione ISO sono indietro rispetto alla tabella di marcia inizialmente prevista, pertanto la versione definitiva non sarà pronta prima dell'inizio del 1999, cioè tra più di due anni. Una vera eternità in campo informatico! Pensiamo solamente che due anni fa, a fine 1994, non era ancora uscito Windows 95, Java non esisteva se non nei documenti confidential di Sun, e nessuno si sarebbe mai sognato gli attuali complicati intrecci tra programmazione ad oggetti e pagine WWW... Vediamo ora quali sono le principali novità. Per ogni nuova feature del C++ faremo un breve paragone con la sua controparte in Java, ovviamente qualora esista.

link articolo oopNamespace

link articolo oopC++. Il namespace sarà un meccanismo che consentirà di inglobare un insieme di dichiarazioni di tipi, constanti, variabili e funzioni dentro un unico contenitore, detto appunto "spazio dei nomi", per risolvere il problema dei conflitti tra identificatori uguali. Per esempio, sarà possibile implementare una libreria matematica Mathlib contenente la classe Point, una libreria grafica GraphLib contentente un'altra classe Point (ovviamente con un significato diverso dal precedente) ed utilizzare entrambe senza problemi in un unico programma, scrivendo i nomi "completamente qualificati" Mathlib::Point e GraphLib::Point ove sorgessero ambiguità. Però, a pensarci bene, il problema è solo stato spostato: bisogna comunque evitare che due software house diverse creino due namespace con lo stesso nome. Esistono due proposte per risolvere questo problema: istituire un registro internazionale (idea caldeggiata dal comitato ANSI), oppure inglobare nel nome del namespace l'indirizzo Internet di chi lo ha prodotto. L'ultima soluzione appare molto elegante in quanto non richiede l'uso di alcun registro (di fatto sfrutta quello già esistente che regola i nomi in Internet). I namespace renderanno del tutto obsoleta la dichiarazione static quando essa viene usata per limitare la visibilità di una variabile o funzione all'unità di compilazione in cui è contenuta.

link articolo oopJava. Sun ha usato un approccio praticamente identico per Java, fatto salvo il diverso nome del costrutto: package anziché namespace. È interessante notare come anche per Java sia stato proposto l'uso degli indirizzi Internet per garantire l'unicità dei nomi di package, anche se questa soluzione non convince tutti perché potrebbe dare luogo a situazioni problematiche (si rileggano i precedenti articoli su Java e C++ per maggiori approfondimenti).

link articolo oopType casting

link articolo oopC++.La capacità di poter modificare praticamente "impunemente" virtualmente qualsiasi tipo di dato in qualsiasi altro è sempre stata la croce e delizia dei programmatori C/C++. Questa libertà è da un lato apprezzata perché permette in certe circostanze di "togliere le castagne dal fuoco" quando alcuni costrutti di programmazione diventano molto involuti, dall'altro detestata perché è una delle principali porte d'ingresso dei bug più insidiosi. D'altronde, secondo i puristi, i costrutti involuti sono quasi sempre sintomo di cattivo stile di programmazione... (però è inevitabile scendere a qualche compromesso quando la data di consegna del prodotto si avvicina!). Compiendo un'analisi sistematica, quali sono i principali motivi per cui si effettua il cast di un tipo in un altro? Essi sono:

La reintepretazione dei bit: pensiamo ad esempio all'estrazione della mantissa da una variabile floating point. La variabile viene convertita in un intero avente la stessa rappresentazione di bit, poi con operazioni di shift e mascheratura si estrae la parte desiderata. La reinterpretazione dei bit è in generale un'operazione molto delicata perché dipende spesso dalla piattaforma hardware su cui gira il programma. Il nuovo ISO C++ definirà un apposito operatore, reinterpret_cast, che viene risolto a compile-time, per questa operazione. La filosofia di Java, invece, è quella di nascondere tutte le caratteristiche dipendenti dalla piattaforma hardware, quindi non permette alcun tipo di reinterpretazione dei bit.

La conversione aritmetica: ad esempio per arrotondare un floating point in un intero. Può introdurre problemi di troncamento se il tipo destinazione è meno "capiente" del tipo origine. Ad esempio, convertire un intero da 32 bit in uno short da 16 implica la perdita dei 16 bit più significativi. In C++ è possibile dichiarare esplicitamente metodi per la conversione di tipo; per esempio, supponendo di avere una classe che implementa numeri in virgola fissa con 8 bit di virgola, ecco un esempio di operatore di cast definito esplicitamente:

Nel frammento di codice: il compilatore si sentirà autorizzato ad effettuare automaticamente la conversione usando l'operatore da noi fornito. Se la cosa non ci piace, basterà evitare di definire l'operatore. Però anche un costruttore con parametro può essere utilizzato implicitamente come operatore di cast. Se per esempio la precedente classe FixedPoint contiene il costruttore allora il compilatore sarà in grado di convertire un floating point in un virgola fissa. In questo caso probabilmente è quello che volevamo; ma se stessimo usando un vettore con il costruttore il compilatore non si farà problemi a compilare allocando un vettore di cinque elementi. In realtà questo codice è probabilmente un nostro errore, ma il compilatore non ce lo segnala. Il nuovo ISO C++ permetterà di marcare alcuni costruttori come explicit, di fatto escludendoli dal meccanismo che permette di utilizzarli come operatori di cast implicito. In Java le cose sono più semplici: tutti i cast devono essere sempre effettuati esplicitamente!

L'aritmetica sui puntatori: usata spesso per ottimizzare l'accesso ad un array. Se non viene effettuata con cautela può portare a deleterie operazioni di scrittura fuori dallo spazio dei dati validi, generando i bug probabilmente più insidiosi. Anche in questo caso Java è drastico: niente aritmetica sui puntatori...

La modifica di attributi: usata ad esempio per rimuovere l'attributo const da una variabile. Tipicamente questo avviene quando si vogliono implementare meccanismi di caching su un oggetto. Pensiamo per esempio ad una classe Matrix per la quale è definito il calcolo del determinante:

Per ragioni di efficienza, vogliamo memorizzare il valore del determinante per non farlo ricalcolare la prossima volta. Nel caso la matrice venisse modificata con setValue(), la variabile detValid invaliderebbe il valore di det forzandone il calcolo ex novo la prossima volta che viene richiamata getDet(). Il metodo getDet() è correttamente dichiarato const, perché semanticamente non modifica la matrice, ma la legge soltanto; tuttavia modifica det e detValid, quindi il compilatore ci segnala un errore. Un trucco potrebbe essere quello di dichiarare un puntatore nonconst equivalente a this, ma con l'attributo const rimosso: Il nuovo ISO C++ ci mette a disposizione uno speciale operando di cast per questi casi, const_cast, che viene risolto a tempo di compilazione. Questo tipo di approccio rimane comunque abbastanza sporco... L'ISO C++ ci viene incontro con la possibilità di dichiarare le variabili det e detValid con l'attributo mutable, che farà capire al compilatore che esse possono essere modificate da metodi const. In Java non esiste alcun attributo const e quindi il problema non si pone.

Conversioni in gerarchie di tipi. Un tipico esempio è la classica conversione da tipo Base a tipo Derived (nelle due puntate precedenti è stato spiegato perché e quando viene usata). Due nuovi operatori, static_cast e dynamic_cast, serviranno a questo scopo: il primo compie la verifica a compile-time (fermando la compilazione se la conversione non è possibile), il secondo a run-time (lanciando un'opportuna eccezione se la conversione non è possibile). In Java queste conversioni, che devono essere sempre esplicite, vengono sempre effettuate con un controllo da parte del compilatore (se riesce a risolvere situazioni più "facili"), oppure del runtime nei casi più complessi.

Per concludere la discussione sui tipi, in ISO C++ esisterà un operatore type_id che permetterà di ottenere una struttura, chiamata type_info, per ispezionare il tipo di un oggetto, o confrontare dinamicamente i tipi di due oggetti. Si tratta di fatto di una standardizzazione di tecniche già note, descritte per esempio nell'arcinoto Annotated Reference Manual di Stroustrup.

link articolo oopJava. Possiamo dire che il C++ si sta muovendo sulla strada dei cast espliciti, che è poi la stessa soluzione (scelta con più convinzione) adottata da Java: ricordiamo infatti che in Java tutti i cast devono essere effettuati esplicitamente. Anche la possibilità di ispezionare dinamicamente il tipo di una variabile è completamente supportata dal linguaggio di Sun. Alcune notizie (di cui per il momento non ho avuto il tempo di cercare conferma) sulle prossime novità di Java parlano di notevoli estensioni di queste capacità, come la possibilità di indirizzare e richiamare un metodo di un oggetto a partire da una stringa contenente il suo nome (!)...

link articolo oopTemplate

link articolo oopC++. I nuovi template consentiranno maggior flessibilità rispetto a quelli attuali. Sarà possibile "templatizzare" solo alcuni metodi di una classe, mentre adesso la granularità è al livello di un'intera classe. I parametri dei template potranno essere anche costanti, e quindi non solo tipi. Questa caratteristica (già permessa da alcuni compilatori esistenti) consente la cosiddetta "metaprogrammazione di template". Considerate questo frammento di codice:

In questo modo possiamo calcolare a tempo di compilazione un qualsiasi fattoriale! Questa tecnica permette di evitare la costruzione di tabelle precalcolate con costanti nel caso esse servano per ottimizzare complessi calcoli matematici. Pensate per esempio ad una trasformata di Fourier: generalmente tutti i coefficienti vanno precalcolati e messi in una tabella. Usando l'espansione in serie polinomiale di seno e coseno e la tecnica di metaprogrammazione dei template è possibile calcolarli in linea durante la compilazione, in modo più elegante ed efficiente (trattandosi di effettive costanti e non variabili, il compilatore potrà generare codice migliore). Ovviamente dovremo pagare un prezzo: una minore velocità di compilazione. Chi è interessato alla metaprogrammazione dei template vada a curiosare su http://monet.waterloo.ca/blitz/ Un problema non risolto dei template C++ è la necessità di dover di fatto distribuire il codice sorgente per poterli utilizzare; inoltre non sono stati ancora formulati metodi standard per evitare duplicazioni inutili di codice eseguibile durante l'istanziazione delle classi "templatizzate" (questo fatto può implicare la generazione di quantità non trascurabili di codice in più).

link articolo oopJava. Ai progettisti di Java "non piace la corrente implementazione dei template". Dietro questa frase probabilmente è nascosta la determinazione di non aumentare la complessità del compilatore, almeno per il momento. Versioni successive di Java molto probabilmente introdurranno qualche nuova forma di template. C'è da dire che in molti casi Java non necessita assolutamente dei template del C++ per motivi di diversa impostazione del progetto. Mentre in C++ ha significato avere, per esempio, un vettore di oggetti "templatizzato", come nel seguente esempio:

in Java si sfrutta il fatto che la classe Object è la radice della gerarchia per tutte le possibili classi definibili, così si può usare sempre un'unica classe Vector che contiene Object: Questo da un lato consente strutture più flessibili, come vettori che contengono oggetti di tipi diversi, senza nessun rischio sulla robustezza del programma, visti i cast espliciti che bisogna utilizzare quando si estraggono gli elementi dai vettori. Si noti anche l'uso di classi wrapper come Integer per far prendere parte al gioco anche i tipi primitivi. Tutto questo, chiaramente, al prezzo di una leggera diminuzione di efficienza. La cosa che può apparire più scomoda, almeno secondo me, è il dover scrivere esplicitamente il cast ogni volta che si estrae un elemento. Sarebbe molto comodo poter definire un template di Vector che associ un tipo fisso ad un vettore ed effettui il cast implicitamente...

link articolo oopStandard binario per i file oggetto

link articolo oopC++. È un campo in cui attualmente regna l'anarchia: virtualmente ogni sistema operativo e produttore di compilatori usa un formato diverso, sia per il layout vero e proprio dei record nel file (chi non ha mai sentito parlare di sigle come a.out, COFF, ELF?), sia per quanto riguarda i pattern dei nomi simbolici, specialmente nel caso del mangling, la tecnica che mappa nel nome di una funzione i tipi dei suoi argomenti. Il risultato è che anche quando in teoria si potrebbe avere compatibilità di codice macchina vi si deve rinunciare. Ad esempio, una libreria di calcolo che non coinvolga input/output evoluto potrebbe benissimo esistere nello stesso formato per Windows 95/NT e Linux/Intel, in quanto i due sistemi operativi sono basati entrambi su architettura 80x86 Intel a 32 bit. Oppure, rimanendo per esempio sotto Windows 95/NT, sarebbe comodo poter usare una libreria compilata con un compilatore Watcom insieme ad un programma ottenuto con il Borland C. Attualmente non è possibile niente di tutto ciò (tranne in casi particolari e comunque con grandi cautele), così un produttore di librerie che non vuole distribuire i sorgenti deve preoccuparsi di acquistare più compilatori per ogni sistema operativo che vuole supportare e preparare diverse versioni dei suoi prodotti. Se non lo fa, si condanna a ridurre il proprio mercato. La situazione rimarrà confusa ancora per molto tempo: i vari produttori sono ancora ben lontani da qualsiasi accordo, nonostante alcune soluzioni tecniche siano state proposte.

link articolo oopJava. È il paradiso al confronto! Tutti i problemi sono stati risolti: il formato dei moduli compilati è unico, condiviso da tutti i compilatori e sistemi operativi. Questo vuol dire che possiamo compilare alcune classi con Symantec Café su Windows 95, altre con il JDK di Sun sotto Solaris su una Sparc ed altre ancora con il compilatore Guavac di GNU su Linux che gira su un Alpha, e poi metterle assieme per formare un unico programma. Per un produttore di software, un unico sistema operativo ed un unico compilatore permettono quindi l'accesso al mercato relativo a tutte le piattaforme (compreso quelle non ancora esistenti!).

link articolo oopVarie

Non ci sono molte altre novità per l'ISO C++... La garbage collection e la persistenza degli oggetti come standard del linguaggio sono state rifiutate (anche per la grande pressione di Stroustrup in tal senso), lasciando ai programmatori la libertà di implementarli eventualmente come librerie. Questa, d'altronde, pare una giusta scelta, data la natura del C++ che consente ampie "customizzazioni" da parte del programmatore. Esistono poi altre proposte, note sotto il nome di C 2000, (nome che ci suggerisce che non si farà in tempo ad includerle nel prossimo standard), tra cui la possibilità di evitare la ricompilazione di classi derivate se cambia qualche dettaglio dell'implementazione delle classi da cui derivano. La discussione qui è ancora in alto mare... Java risolve elegantemente anche questo problema, dal momento che non è il compilatore, ma il linker, a stabilire il layout della memoria.

link articolo oopPer concludere

Molti problemi del C++ sono stati correttamente affrontati dal comitato di standardizzazione e sono state trovate delle opportune soluzioni. Il C++ del futuro sarà un linguaggio sempre più robusto e meno legato a certe alchimie del suo antenato C; tuttavia molti problemi rimangono sul tappeto, e come già detto, i tempi dell'evoluzione si preannunciano comunque lenti, a causa della molteplicità di partner da mettere d'accordo. D'altro canto per Java i tempi saranno sempre più veloci rispetto al C++ (basti pensare a come nuove API standard vengano continuamente annunciate con una velocità talmente elevata che a volte pare impossibile mantenersi aggiornati), grazie al fatto che Sun è l'unica "padrona" del linguaggio. Un'ultima considerazione. Quando si parla di implementazione standard ci si riferisce sempre ad un compilatore ideale, perfetto, privo di difetti. Quando si passa poi alla pratica si ha ovviamente a che fare con compilatori reali, che hanno dimostrato di avere spesso qualche difetto di troppo, piccolo o grande, attorno al quale si è costretti a girare con qualche acrobazia. Questo fatto è dovuto, tra le altre cose, alla inerente complessità di un compilatore C++, direttamente discendente dalla complessità del linguaggio stesso. Sun, d'altro canto, continua a ribadire il concetto per cui la grammatica e la struttura di Java implicano un compilatore più leggero e compatto, e quindi anche le probabilità di difetti vengono drasticamente ridotte. Morale della favola? Sun promette che Java riuscirà ad elevare il livello della qualità del software applicativo nei prossimi anni. D'altronde oggi è esperienza comune scontrarsi in continuazione con bachi, gravi o meno gravi, che affliggono indifferentemente programmi di pubblico dominio o commerciali (compreso ben noti sistemi operativi...). Sarà una promessa mantenuta? Vedremo, aspettiamo intanto l'uscita dei primi applicativi "pesanti" scritti in Java...

 
 

MokaByte rivista web su Java

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