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 ...