L'angolo di 6502C oppure C++ ?
2002-12-28
Indice

Benvenuti!
Chi sono
Demo
Documenti
Quelli piccoli
Problemi
Scacchi
Immagini
Musica
Il mio blog
Scrivimi

English version 


C oppure C++ ?

Qualche anno fa avevo deciso di imparare questo nuovo linguaggio object oriented (arrivavo dal Borland Pascal with objects ed ero abbastanza entusiasta di questo nuovo modo di programmare).

Purtroppo la mia prima impressione e' stata decisamente negativa sotto molti punti di vista e ho quindi deciso di abbandonare quello che non ritenevo fosse un buon strumento di lavoro.

Recentemente ho pero' deciso di riconsiderare il C++ e di provare ad analizzare questo relativamente nuovo linguaggio allo stato attuale. Ora la situazione dovrebbe essere piu' stabile e standardizzata.

Seguendo il consiglio di un mio amico molto piu' esperto di me in C++ ho acquistato i seguenti volumi:
  
The C++ programming language (third edition)

Questo e' senz'altro il testo di riferimento per chi vuole imparare il C++ in tutti i suoi meandri sintattici e le sfumature filosofche. L'autore Bjarne Stroustroup e' il padre stesso del linguaggio e questo libro sicuramente non puo' mancare dagli scaffali di un programmatore C++.

Il libro e' a mio parere ben fatto anche se l'amore paterno dell'autore verso il linguaggio fa perdere a volte l'obbiettivita' verso alcuni evidenti problemi o scomodita' presenti nel C++.

Un punto in cui a mio parere si rasenta il ridicolo e' quando la contorta sintassi degli operatori di typecast viene indicata come un pregio visto che i typecast si dovrebbero usare con il contagocce e visto che semplifica la ricerca all'interno dei sorgenti dei typecast stessi. A parte che non mi e' capitato spesso di dire "dove saranno tutti i typecast del mio progetto di 50K linee ?" trovo assurdo indicare una sintassi scomoda come un punto a favore anche se la cosa riguarda una feature da usare raramente: a questo punto immagino che se l'inline assembler che alcuni compilatori permettono richiedesse l'inserimento dei codici esadecimali delle istruzioni il compilatore dovrebbe essere definito migliore...

Questo e' dei testi che ho preso anche l'unico in cui vengano spiegati i template in tutta la loro potenza e complessita'. I template rappresentano senz'altro una delle novita' introdotte dal C++ e Stroustrup e' sicuramente una delle persone che piu' sono convinte di questo tipo di programmazione.

  
Effective C++ CD

Questo libro in formato elettronico rappresenta la fusione di due best-seller e articoli molto interessanti sul C++. Anche il formato del libro (basato su HTML/Java) e' molto curato.

Sostanzialemente si tratta di una serie di problemi specifici di implementazione e progettazione affrontati in grande dettaglio e fortemente intercorrelati fra loro. Si tratta di un libro interamente dedicato al C++ e che offre spesso soluzioni eleganti e spiegazioni chiare di come questo linguaggio possa aiutare nella soluzione di problematiche abbastanza comuni.

Nei due libri e negli articoli sono anche affrontate in grande dettaglio alcune questioni delicate di tipo "filosofico" sul modo di affrontare la progettazione della gerarchia di classi. Non si tratta sicuramente di un testo sul disegno object-oriented ma alcuni punti solo illuminanti su come le classi C++ dovrebbero essere utilizzate.

Ho trovato solo alcuni punti discutibili, come la ripresa della discussione sui typecast e una (a mio parere) inutile digressione sul conteggio degli oggetti di una determinata classe. Anche alcuni dei problemi relativi all'ordine di inizializzazione di istanze statiche sono stati affrontati in maniera solo parziale.

  
C++ FAQs

Questo libro e' stato scritto in forma di Frequently Asked Questions e questo ha sicuramente facilitato il compito degli autori. Questa forma devo pero' dire che si e' rivelata molto gradevole come lettura.

Diversamente dagli altri due testi questo libro contiene molte discussioni che possono essere riportate senza alcuna modifica anche in ambiti diversi dal C++. Addiruttura molti consigli prescindono anche la programmazione Object Oriented ma riguardano in generale la gestione di grossi progetti. Notevoli sono alcune domande/risposte sulla visione dei progetti informatici e delle risorse umane da parte delle grosse compagnie (io posso assolutamente confermare alcuni dei punti come ad esempio il fatto che si consideri importante che i programmatori siano rimpiazzabili, controllabili e gestibili quando invece i coordinatori cambiano con frequenza piu' alta delle risorse coordinate).

Il libro non contiene una spiegazione del C++, ma piuttosto espone la visione di un particolare utilizzo del C++ in progetti di grosse dimensioni. Esiste una distinzione fondamentale fra quello che e' "legale" in C++ e quello che e' "morale" fare anche se sicuramente il punto di vista esposto e' solo uno dei possibili punti di vista sull'argomento. Devo pero' notare che molte delle tecniche "morali" da seguire facevano gia' parte del mio bagaglio di programmatore C.

Ho trovato questo libro veramente molto interessante. Mi ha solo deluso un po' la discussione riguardo CORBA/DCOM che ho trovato estremamente superficiale e alquanto di parte. Sono persino giunto a sospettare che si tratti di una aggiunta dell'ultimo minuto per rendere piu' interessante la copertina.

La mia opinione sul C++

Sin da quando ho scoperto la programmazione a oggetti con il Borland Pascal mi sono appassionato a questo tipo di visione. Anche quando lavoravo in C ho sempre utilizzato un approccio di suddivisione del problema in "oggetti intelligenti" che implementavo con strutture. L'ereditarieta' (semplice) la implementavo facendo in modo che l'oggetto derivato avesse come primo campo una struttura oggetto-padre e che quindi le funzioni che accettavano un puntatore a oggetto padre potessero anche lavorare sull'oggetto derivato. Dove era necessario il polimorfismo definivo direttamente dei puntatori a funzione in tutte le istanze (non usavo cioe' l'indirezione VMT).

Il mio utilizzo del C++ non era molto diverso dall'uso del C con la differenza che l'ereditarieta' e il polimorfismo erano gestiti direttamente dal compilatore. In particolare avevo mantenuto la pessima abitudine di non incapsulare realmente gli oggetti (tutto era public) e non mi facevo problemi ad agire su oggetti dall'esterno modificando direttamente i campi.

La visione "morale" del C++ e' pero' completamente diversa e provero' gia' dal prossimo progetto hi-level ad approcciare il problema in maniera piu' "pulita".

Il C++ e' sicuramente un linguaggio DIVERSO dal C. Non si tratta di una estensione o di una aggiunta: quello che cambia e' proprio la filosofia di base. Il compilatore C++ fa molte cose in piu' rispetto al compilatore C (e la cosa si nota se si osservano i tempi di compilazione) e questo permette di scrivere codice sicuramente piu' compatto.

Ci sono alcune cose del C++ che per il momento non mi piacciono affatto; in particolare posso citare:
  
Sintassi

La sintassi del C++ che e' sicuramente abbastanza compatta a prima vista mi sembra decisamente "sporca". Non esiste un insieme semplice di regole che descrive il linguaggio ma ci sono piuttosto molti casi particolari che onestamente mi spaventerebbero se dovessi essere chiamato a scrivere un compilatore C++.

Al confronto la sintassi del C e' sicuramente piu' semplice ed elegante e devo confessare che anche la scelta C di demandare molto del resto a delle LIBRERIE invece che al linguaggio mi piace molto (su molti sistemi embedded si usa il C senza usare nessuna delle librerie standard).

Questa distinzione fra linguaggio e ambiente non e' cosi' netta nel C++. In altre parole il C++ e' decisamente piu' monolitico del C. D'altra parte questo dipende dal fatto che il C++ non e' il C e che si tratta di un linguaggio (ambiente?) di programmazione di livello decisamente piu' alto.

  
Template

Confesso che trovo la programmazione di template affascinante e che mi sembra che questo tipo di approccio sia molto meglio delle limitatissime macro del preprocessore C.

Quando pero' mi trovo a usare i template per cose poco piu' che banali mi scontro con una serie di fastidiosi problemi che ne rendono lo sviluppo complesso. Primo fra tutti la quasi incomprensibilita' dei messaggi di errore da parte dei compilatori. Della complessita' di scrittura di codice template si deve essere sicuramente accorto anche Stroustrup se nel suo libro ho trovato un forte consiglio di scrivere prima codice concreto per poi passare a codice parametrico solo quando si e' sicuri che tutto funziona correttamente.

Se poi ci si vuole veramente divertire consiglio di dare un'occhiata alle definizioni dei template standard C++ e anche di immaginare cosa significhi per una persona fare manutenzione su codice come quello dopo qualche anno che non lo si guarda.

Spero che i problemi di compilazione template si risolvano con il tempo e con il miglioramento dei compilatori. La mia posizione resta comunque abbastanza critica verso questo strumento che pur potente rischia di essere difficilmente dominabile sotto diversi aspetti.

  
STL e libreria standard

La libreria standard del C++ e' decisamente piu' vasta di quella del C ed in particolare spicca la gestione dei contenitori e degli algoritmi che ora e' parte integrante dello standard ANSI.

Per quello che riguarda i contenitori devo ammettere che vengono forniti una serie di strumenti di base che permettono di scrivere programmi di poche righe che fanno cose che in C richiederebbero tempi di sviluppo notevoli. Spiccano pero' a mio parere l'assenza (inspiegabile) di strutture fondamentali quali una hash-map e la definizione di una serie di template standard per la gestione di handle con reference count.

Sugli algoritmi sono decisamente perplesso. L'unico testo che ne parla diffusamente e' "The C++ programming language" e quindi penso che si tratti di qualcosa che non viene utilizzato molto spesso. Anche considerando ammirevole l'obbiettivo (e non so se questa e' la mia opinione) ritengo che il risultato sia un po' troppo complesso e poco leggibile ... forse si tratta pero' di fare l'occhio alla cosa (dopotutto anche la sintassi PERL sembra terribile al primo impatto, ma non e' affatto male una volta che si sono fatti i primi passi).

La gestione C++ dell'I/O e' forse la cosa del linguaggio che mi piace meno. La estendibilita' dell'I/O di classi si sarebbe potuta ottenere anche senza trasformare le primitive di I/O in operatori (scelta molto discutibile). Trovo anche che la gestione della formattazione sia decisamente scomoda come utilizzo e anche "sporca" da un punto di vista "filosofico" di programmazione ad oggetti. Per l'I/O io avrei preferito decisamente la definizione di una funzione membro "standard" che ricevesse lo stream di output e il formato come parametri. Purtroppo ho l'impressione che gran parte dei problemi siano dovuti al fatto di voler a tutti i costi mantenere una sintassi simile per classi e tipi base che non sono classi (forse il fatto stesso che i tipi base non siano classi e' uno dei problemi di fondo della sintassi C++ in generale, e io non sono convito che tentare di nascondere questa differenza sia la cosa piu' giusta da fare).

  
Exceptions

La gestione delle exceptions e' sicuramente una delle altre grosse novita' del C++. Io sono perplesso riguardo a questo tipo di gestione degli errori per il fatto che in C++ non e' un linguaggio di livello abbastanza alto.

Provo a spiegarmi meglio.

Io personalmente ho in passato scritto un interprete che usa per la gestione degli errori un metodo molto simile alle exception C++. La sintassi che ho usato e' simile al Pascal ma prevede la clausa "on error do <statement>" nei blocchi begin/end. Quando si verifica un errore (di sistema o generato con "error <value>") l'interprete esegue lo stack-unwinding sino al primo blocco di on error attivo.

Ovviamente la gestione eccezione C++ e' piu' sofisticata ma la differenza principale sta a mio parere che nel caso dell'interprete che ho scritto il linguaggio era typeless basato su puntatori con reference-count automatico e che lo stack unwinding non puo' portare a perdita di risorse. Anche i file sono ad esempio gestiti con oggetti che, se distrutti, provvedono a effettuare le necessarie chiamate chiusura al sistema operativo.

Il linguaggio e' inoltre di tipo procedurale e, pur restando un possibile problema con variabili globali lasciate in stato non corretto, lo stack unwinding non ha una grossa probabilita' di generare danni. Il linguaggio e' stato scritto per un server transazionale senza contesto e quindi le poche variabili globali sono utilizzate nella sola parte generale e non nell'implementazione delle transazioni (dove risiede la maggior parte del codice).

La situazione nel C++ e' completamente diversa. In particolare ci sono problemi sia con i raw pointer (che sono fortemente sconsigliati proprio per questa ragione) che con lo stato degli oggetti. Se infatti l'uso sistematico di auto_ptr o di handle con reference count (che purtroppo non fanno parte del corredo standard) permette di evitare la perdita di risorse, resta il problema dello stato di un oggetto che viene attraversato dallo stack unwinding di una exception.

In altre parole la presenza di eccezioni implica che in qualunque momento (o quasi) il flusso normale di esecuzione possa essere interrotto per "scappare fuori" dal metodo. Quando questo avviene bisogna essere certi di non perdere risorse (abbastanza facile) e di non lasciare le cose in disordine (cosa piu' complicata specie se si considera che una exception puo' essere generata nel mezzo del calcolo di una espressione in cui l'ordine di valutazione e' spesso scelto arbitrariamente dal compilatore).

Come viene ben descritto in un articolo di "Effective C++ CD" il problema non e' in chi genera l'exception (throw) o in chi la gestisce (catch) ma in tutte le classi che l'exception attraversa in maniera quasi asincrona. Sicuramente i casi banali sono molto eleganti e compatti; purtroppo ho l'impressione che la gestione exception non sia facilemente scalabile a sistemi object oriented complessi (a meno di avere moltissimi costrutti di catch che pero' annullano i vantaggi delle exceptions rispetto ad una normale gestione a return code).

Diverso sarebbe se il compilatore fornisse la possibilita' di descrivere nei metodi delle "transazioni" simili a quelle presenti nei database relazionali che garantiscono che di un gruppo di operazioni o non ne viene eseguita alcuna o vengono eseguite tutte (con gestione di transazioni nidificate). L'interprete da me scritto utilizzava appunto questo approccio, incapsulando tutta la transazione server in un blocco begin/end che nella sezione on-error contiene una chiamata "rollback" al database.

Note:

Dopo qualche riflessione ho trovato che i template C++ sono sufficientemente potenti da permettere l'implementazione delle transazioni sulle modifiche degli oggetti con poche linee di codice (con qualche limitazione). Questo ovviamente penalizza le performance e ha qualche altra controindicazione.

Anche considerando queste limitazioni ho deciso di utilizzare le exception e le transazioni in un progetto che sviluppero' nei prossimi mesi. Una delle parti in cui utilizzero' questa tecnica e' un server transazionale senza contesto che lavora su un database reticolare di classi C++ in memoria.

Seguiranno dettagli.

Conclusioni

Sono abbastanza confuso da questo tipo di linguaggio; ritengo che il matrimonio fra il pulito e semplice C con il C++ non sia un'ottima scelta. I punto base del C era il completo controllo da parte del programmatore di cosa avviene (si potevano quasi immaginare le istruzioni assembler necessarie per compilare un determinato frammento di codice). In C++ questa certezza viene decisamente meno.

Ho anche l'impressione che gli assertori del C++ spesso vedano questo linguaggio come un sistema per programmare capendo un po' meno quello che avviene "dietro le quinte". Il sono personalmente contrario a questo tipo di visione e credo che l'approccio ad oggetti sia un modo di organizzare la conoscenza e non un modo di evitarla. Utilizzare qualcosa senza conoscere (almeno a grandi linee) come la cosa e' effettivamente implementata e' un modo sicuro di cercarsi delle grane. Prima o poi si faranno dei grossolani errori di utilizzo, impegando a sproposito oggetti che non si erano realmente compresi.

Pur restando un tifoso del C devo pero' ammettere che per programmare object oriented il C++ offre una marcia in piu' notevole. Inoltre la filosofia strong-typed permette al compilatore di generare codice che e' di grande efficienza e che permette di utilizzare un approccio object-oriented anche in ambiti in cui le prestazioni sono una variabile importante. Io sono un amante di sistemi piu' "dinamici" ma l'efficienza raggiungibile da un sistema strong-typed fa sicuramente del C++ un oggetto prezioso.

Anche le asimmetrie sintattiche del C++ non sono state pensate per il semplice gusto della stravaganza; si tratta di asimmetrie che aumentano la comodita' di utilizzo. Sicuramente questi elementi "sporcano" la sintassi e rendono la vita difficile ai programmatori di compilatori (pochi) ma semplificano la vita ai programmatori di applicazioni (molti).

Trovo inspiegabile la carenza di template standard di gestione smart pointer: pur restando ovvio che il C++ e' interamente costruito sulla semantica di copia, una gestione di puntatori intelligenti standard sarebbe risultata molto comoda per quegli ambiti in cui la semantica di copy fallisce (ad. es. contenitori eterogenei).

Ora per farmi un'idea piu' precisa non mi resta che provare a lavorare in C++ "vero" ad un progetto di almeno 100Klinee (molto meglio se congiuntamente ad altri programmatori, almeno secondo "C++ FAQs").

Qualcuno ha detto ...

- Dal TAO DELLA PROGRAMMAZIONE -

In principio vi era solo il linguaggio macchina.
  Poi vennero gli assemblatori.
    E poi i compilatori.

E da allora i linguaggi si sono moltiplicati numerosi
  nel nostro piccolo mondo di logica.

Ogni linguaggio ha il suo carattere e la sua forza.
  Ogni linguaggio ha pari dignita' nel Tao.

Se pero' ti e' possibile
  evita di usare il COBOL.