Newsgroup: it.comp.lang.c++
Titolo: difetti di c e c++: quattro domande

Salve, č da circa 5 anni che sono passato dal Pascal al C, e poi al C++. Col
tempo ho imparato ad apprezzare i vantaggi questo linguaggio, che č quello
che uso di pių, anche quelli che a prima vista possono sembrare difetti.
Tuttavia ci sono alcuni punti che ancora non mi riesco a spiegare. La
particolaritā č che tutte queste, che io definirei "carenze", non sono
presenti nel Pascal. Non č per fare confronti (se dovessi elencare tutti i
punti in cui il C č superiore al Pascal non finirei pių), semplicemente in
qualche modo mi meraviglia, perchč il Pascal č stato, se non sbaglio, uno
dei principali linguaggi da cui č evoluto il C.
Pių che difetti quindi sarebbe opportuno chiamarle domande, che rivolgo a
chi a pių esperienza.
1 - Il range check sugli indici degli array.
Sappiamo benissimo che questo tipo di controlli rallentano incredibilmente
il programma, tuttavia č anche vero che l'assenza di tali controlli č senza
dubbio la maggiore fonte di bug gravi e difficili da scovare. Un controllo
"facoltativo" diponibile come opzione di compilazione, da attivare solo
nella fase di sviluppo e debug, salverebbe, come si dice, capra e cavoli. E
invece si č costretti a ricorrere a tool esterni, come se tale servizio non
meritasse di essere incluso nel compilatore.
Quale sarebbe dunque il vantaggio di tale assenza?
Con il C++ la situazione migliora, ma comunque, se si vuole avere tale
controllo bisogna ricorre a strutture che sono pių complesse e lente dei
semplici array, e che rimangono tali anche nella versione definitiva.
2 - Il tipo stringa.
Tutti i linguaggi che supportano i "tipi" hanno, tra i vari tipi base, la
stringa, non č una prerogativa dei linguaggi pių evoluti o ad oggetti.
Perchč invece il C non supporta alcun tipo stringa?
E vero che i puntatori a carattere sono pių efficenti, anche se molto pių
scomodi e fonte di errori, ma la presenza di un tipo stringa non avrebbe
comunque precluso, a chi avesso voluto, l'uso dei puntatori a carettere.
3 - Sintassi "perversa" del costrutto switch/case.
Questa č una delle cose che mi incuriosisce di pių: come tutti sappiamo,
nell'istruzione switch() il compilatore salta alla prima "case" che soddisfa
la condizione imposta, esegue tutte le istruzioni seguenti, e poi, - contro
ogni logica - esegue a cascata anche tutti gli altri "case" seguenti,
compresi quelli che non soddifano la condizione, e persino il "default".
L'unico modo per poter avere un comportamento quasi "normale" del programma
č aggiungere delle istruzioni "break" al termine di ogni case. In questo
modo, perō, se per ipotesi ci fossero altri case "veri" pių gių, non
verranno eseguiti, quindi a rigor di logica sarebbe ancora sbagliato. In
effetti l'uso di "break" dopo il case č cosė diffuso che ormai viene
considerato quasi come facesse parte del costrutto switch, ma in realtā non
ha niente a che fare con questo: break č un estruzione di salto, come "goto"
e "continue", e come tale dovrebbe essere usata il meno possibile, se non
addirittura bandita, secondo le regole della programmazione strutturata. Nel
C (e anche nel C++) invece, non viene solo permessa, ma, di fatto, si č
addirittura costretti a usarla, ci siamo talmente abituati che non ci
facciamo pių caso, ma essere costretti a usare break per uscire da uno
"switch" č come se fossimo costretti a usare break per uscire da un "for" o
da un "while". Il fatto che in realtā non fa parte del costrutto switch lo
si nota quando, per caso ci si dimentica di metterla: il compilatore non dā
neanche un warning, il programma parte e ci teniamo un altro di quegli
errori non facilmente ritracciabili.
La domanda dunque č: esiste veramente una situazione in cui tale
comportamento dello switch risulta tanto vantaggioso da giustificare una
sintassi cosė illogica?
4 - assenza di un costrutto "with".
Immaginamo di dover eseguire un insieme di istruzioni per riempire tutti i
campi di un dato elemento, come in questo esempio:
        vett[al*i+j].x1=10*precx;
        vett[al*i+j].y1=10*precy;
        vett[al*i+j].x2=10*posx;
        vett[al*i+j].y2=10*posy;
        vett[al*i+j].lettera='p';
        vett[al*i+j].lung=vett[al*i+j].x2-vett[al*i+j].x1;
        vett[al*i+j].alt=vett[al*i+j].y2-vett[al*i+j].y1;
vett[al*i+j].mem=malloc(vett[al*i+j].lung*vett[al*i+j].alt*sizeof(int));
č evidente l'enorme uso del cut&paste, la perdita di tempo, la possibile
fonte di errori, e la mancata ottimizzazione di questo tipo di scrittura,
che, almeno a me, capita enormemente spesso. Eppure
per quanto ci ho pensato non sono riuscito a trovare alcun possibile
miglioramento che vada oltre una sostituzione: index=al*i+j;
immaginiamo ora l'esistenza di un ipotetico costrutto with che mi permetta
di scrivere la stessa cosa nel seguente modo:
  with( vett[al*i+j] )
    {
        x1=10*precx;
        y1=10*precy;
        x2=10*posx;
        y2=10*posy;
        lettera='p';
        lung=x2-x1;
        alt=y2-y1;
        mem=malloc(lung*alt*sizeof(int));
   }
č palese l'enorme vantaggio non solo in termini di leggibilitā, di eleganza,
facilitā di scrittura, difficoltā di errore, ma anche di compattezza del
codice, sia sorgente che compilato, e di velocitā di esecuzione (a meno che
il compilatore non faccia giā questo tipo di ottimizzazioni in maniera
automatica). Neanche si puō dire che questa istruzione sia una mia idea, a
cui i progettisti del C non avevano pensato, infatti esiste, ed č
usatissima, nel linguaggio Pascal, quindi si suppone che se non l'hanno
voluta implementare ci sarā una ragione. Qual č, secondo voi?

         vett[al*i+j].lung=vett[al*i+j].x2-vett[al*i+j].x1;
Poniamo che vett sia un array di mystruct_t.
         mystruct_t & e = vett[al*i+j];
         e.x1=10*precx;
         e.y1=10*precy;
         e.x2=10*posx;
         e.y2=10*posy;
         e.lettera='p';
         e.lung=e.x2-e.x1;
Max

 Qual è, secondo voi?
semplice: fai una classe per il vettore, un semplice contenimento di 
arrawy, e fai una funzione membro che lo fa per te. Se non puoi/vuoi, 
usi std::trasform, cosi'
template <typename T> 
void do_stuff(T v){
   v.x1=10*precx;
   v.y1=10*precy;
   v.x2=10*posx;
   v.y2=10*posy;
   v.lettera='p';
   v.lung=v.x2-v.x1;
   v.alt=v.y2-v.y1;
   v.mem=malloc(v.lung*v.alt*sizeof(int));
}
std::trasform(vett[0], vett[MAX], vett[0], do_stuff);
non mi sembra tutta questa enorme complicazione

 void do_stuff(T v){
 void do_stuff( T & v ){
Max

>verranno eseguiti, quindi a rigor di logica sarebbe ancora sbagliato.
Tommy

 addirittura bandita, secondo le regole della programmazione strutturata. 
Anche le istruzioni if (then) else for, while, do, nascondono dei goto e
non sono "piu'" strutturati del switch-case-break: hanno semplicemente
un'altra sintassi.
Comunque non trovo assolutamente niente di scandaloso nell'uso
dei goto: in alcuni casi permettono di avere del codice
piu' leggibile, sensato, aderente all'algoritmo che uno sta
implementando (specialmente in C, dove non c'e' la gestione dell'errore 
come nel C++). In diversi algoritmi di carattere
matematico/combinatorio che ho utilizzato in C mi e' capitato di
avere piu' cicli annidati dai quali uscire sotto certe condizioni:
e' chiaro che puoi fare a meno del goto inserendo vari flag
che inducano il programma ad uscire dal ciclo, ma questo
porta facilmente a programmi piu' lenti e meno aderenti
allo spirito originario dell'algoritmo.
E' chiaro che abusare del goto e' dannoso, cosi'
come a volte sono assurdi certi programmi dei nuovi convertiti al C++
che non sono soddisfatti se non hanno definito almeno 20 classi
per un programma che fa 2+2.
Per concludere questo mio intervento abbastanza sconclusionato, io 
sinceramente non cerco una logica intrinseca in un linguaggio,
lo vedo come uno strumento per ottenere uno scopo e piu' frecce ho al
mio arco e meglio sto.
ciao,
g.

  void do_stuff( T & v ){
oops, vero

 4 - assenza di un costrutto "with".
Su questo sono d'accordo,sarebbe una comodità,ma niente di più.Non
avrebbe vantaggi una volta compilato. Ma probabilmente sarebbe comodo in
un ambiente orientato agli oggetti.

 [...]
Non capisco... se la libreria standard facesse parte del linguaggio, vector
(senza bisogno di std::) sarebbe una parola chiave che identifica il tipo
primitivo vector, string (senza bisogno di std::) sarebbe una parola chiave
che identifica il tipo primitivo string, e così via. Quindi è perfettamente
legittimo considerare la libreria standard come qualcosa di distinto dal
"linguaggio c++", al contrario di altri arricchimenti adottati a livello di
linguaggio vero e proprio, come per esempio i template. O mi sbaglio ?
Ciao!


Che sia o non sia sempre possibile e utile riunciare ai salti è stato un
tema su cui ognuno a le idee sue. In ogni caso mi sembra giustissimo che il
C non obblighi a stili di programmazione che potrebbero non essere consivisi
da tutti, e quindi permetta, volendolo, di usare i salti. Il paradosso a cui
mi riferivo e che nel caso dell'istruzione switch non "permette", ma,
praticamente, "obbliga" a usare il break. Farne un uso sistematico ad ogni
istruzione switch è un altro discorso rispetto a usarlo in via eccezionale e
con molta parsimonia per uscire da un ciclo in situazioni particolari.

"linguaggio c++",
la stl è ora considerata parte integrante del linguaggio..
la libreria standard _fa_ parte del linguaggio...
ciao
 sbirish

 voluta implementare ci sarà una ragione. Qual è, secondo voi?
Quelle sopra elencate :)
Saluti
--
NinjaCross
 
www.ninjacross.it
Membro dell'Italian Agile Movement.
www.agilemovement.it
Sconnetti il chip isolineare per contattarmi
ninjacross_CH...@yahoo.it

 break;
e questo come lo fai:
switch(foo) {
case 1:
	init();
case 2:
case 3:
	do_something();
case 4:
	do_something_else();
	break;
case 5:
	foo++;
	break;
}

  sbirish

>  sbirish
Ciò che era prima la STL ora è stata inclusa nella libreria
standard,anche se il comitato non riconosce il nome "STL"...


cosě, e sarebbe anche molto piů leggibile:
 switch(foo) {
 case 1:
 init();
 case 1,2,3:
 do_something();
 case 1,2,3,4:
 do_something_else();
  case 5:
 foo++;
}
e in piů potresti fare anche questo:
switch(foo){
case 1:
  /*  prima parte codice A */
case 2:
 /* prima parte codice B */
case 1,2:
 /* seconda parte, comune ad A e B */
case 3:
 /* codice C */
}
tutto questo si potrebbe fare SE il flusso non cadesse a cascata tra i case,
se fossero permessi i case multipli (case 1,2) e se non fosse vietato il
duplicate identifier. Certo, sarebbe concettualmente snaturato il vero
significato dello switch, in base al quale dovrebbe essere eseguito solo uno
dei blocchi case, ma questo viene snaturato anche con l'esempio che hai
fatto tu.

 wrote in message
2vsa5vgc08n3ve52s...@4ax.com...

a prescindere dal fatto che la libreria standard faccia o no parte del
linguaggio in ogni caso il tipo stringa non esiste come tipo base ne' nel C,
ne' nel C++ (nel quale l'unico tipo base introdotto è il "bool"), solo che,
mentre nel C++ la flessibilità del linguaggio permette di crearsi facilmente
un tipo stringa secondo i propri gusti, o attingere a una delle tante
librerie esistenti, compresa quella standard; nel C questo non è possibile,
pertanto la mancanza di un tipo stringa si sente, e pensantemente,
costringendo all'uso dei puntatori a caratteri senza altra alternativa.


Per quanto riguarda il codice sorgente la compattezza, intesa come
eliminazione di ridonanza inutile, mi sembra fuori discussione, e poi
secondo te gli operatori ?: e ++ perchè esistono? non sono anche queste
sottigliezze?
Riguardo al codice compilato invece devo ammettere che non l'ho mai
esaminato, quindi riconosco la mia ignoranza. Se ho scritto questo è perchè,
a intuito, ho pensato questo: senza with posso sperare che, nella migliore
delle ipotesi, il compilatore si accorga comunque da solo che usiamo sempre
lo stesso indice e lo memorizzi in un registro senza doversi rifare tutte le
moltplicazioni ogni volta, forse arriva da solo anche a crearsi un
riferimento diretto alla variabile in questione ma non può sapere, a priori
qual è il blocco di istruzioni su cui deve applicare queste ottimizzazioni,
quindi, se glie lo dico io, male non gli fa di sicuro.
A te risulta che faccia qualcos'altro?

 senza altra alternativa.
 
Infatti il C++ è nato per superare i limiti intrinseci del C...e il C è
così per precisa scelta degli autori...e comunque non è tanto male,visto
che i migliori sistemi operativi del momento sono stati scritti in C, e
molti dei programmi di maggior successo degli anni passati sono stati
scritti in C. fermo restando che oggi come oggi il C++ rappresenta
un'evoluzione enorme...

 Ah... la libreria standard non è la stl!
tecnicamente, potrebbe non esistere nessun file degli include in forma 
esplicita, ma essere tutti incorporati nel compilatore, e l'#includere 
un file equivale ad attivare alcune features...

 dei puntatori a caratteri senza altra alternativa.
Non credo proprio che sia vero.
Volendo, in C, si potrebbe creare un tipo "string" sufficiente per
le più comuni necessità.
Comunque mi pare che tu faccia confusione tra C e C++, molte
volte nei tuoi messaggi pare che ne indichi uno intendendo l'altro.
Infine, come ti hanno già risposto in molti, i tuoi problemi si risolvono,
nell'ordine, con
1- std::vector e at()
2- std::string
3- Domanda mal posta. Lo switch è più flessibile così, anche se
    indubbiamente un po' più criptico.
4- std::for_each e std::transform
comunque, se le caratteristiche che cerchi in un linguaggio sono
quelle che elenchi, personalemtne mi rivolgerei a linguaggi più "puliti"
e di alto livello, uno su tutti il Python.
ciao
--
Gerolamo
E' in corso la votazione di it.comp.software.webserver
Se sei interessato alla creazione di questo NG, leggi il messaggio:
news:MK.it.comp.softw...@news.nic.it

strutturata, perchè non isolarlo e porre in testa il nome della variabile?
Perché questo porta molto facilmente a compiere errori difficilissimi
da scovare (vedi due with annidati o un with riferito a due
oggetti/record).  L'uso del "with" non è una buona pratica di
programmazione (io, in Delphi, lo evito sempre) perché molto spesso
tende a creare più confusione che altro.
-- 
Andrea Laforgia
"God is dead." -- Nietzsche
"Nietzsche is dead." -- God