Newsgroup: it.comp.lang.c++
Titolo: insoddisfatto del proprio codice

Ciao a tutti,
e' da un po' che programmo in c++; programmo in ambito scientifico e
sono parecchio insoddisfatto del mio codice perche' poco
riutilizzabile fondamentalmente. Ma non e' tutto, a volte ci scappano
dei leaks ed andarli a riprendere e' lavoro alquanto tedioso perche'
lavoriamo con strumenti che occupano carrellate di ram per darci delle
informazioni per altro incomplete.
Vi chiedo se potete consigliarme uno o piu' libri che aiutino a
generare del buon codice...si e' generico, infatti per buon codice
intendo :
1) buoni smart pointers
2) un sistema di controllo di memory leaks all'interno di un proprio
framework senza usare programmi terzi che sondano il proprio codice
3) possiblita' di non impazzire per generarsi delle dll e magari avere
la possibilita' di scriptarlo
4) ridefinirsi template o classi dell' STL
A parte i vari Design Patterns che ti insegnano a livello teorico i
pattern dove deve evolvere il proprio codice, come si fa ad avere una
summa di queste qualita', magari agendo anche su macro, per avere
codice ben fatto, pulito, riutilizzabile?
grazie a tutti
p.s. so che e' un post un po' vago e delirante, ma mi son rotto di
pescare qua e la' info, la mia conoscenza in merito mi sembra fatta a
pezze, e mi piacerebbe avere un buon testo che riassume o cmq su cui
fare riferimento. O piu' di uno se e' il caso.

Stai sbagliando obiettivo, per avere codice pulito e riutilizzabile ti 
devi concentrare sul design del software, i tre punti che hai scritto 
riguardano la scrittura spicciola del codice.
Il codice pulito non si ottiene utilizzando gli smart pointer, o 
imparando a scrivere i template. Perlomeno non solo quello.
Per avere un codice pulito devi imparare a progettare la tua 
applicazione o le sue librerie.
Il progetto (o design) fa parte del processo di sviluppo, per riuscire a 
  generare codice pulito e riutilizzabile devi adottare una delle tante 
metodologie di sviluppo.
Esistono tante metodologie di sviluppo e ognuna ha un campo di 
applicazione dove è più appropriata delle altre.
Le metodologie di sviluppo moderne sono per lo più orientate agli 
oggetti e hanno soppiantato le metodologie strutturate.
Un buon libro che spiega come affrontare il design orientato agli 
oggetti è :
Object-Oriented Analysis and Design
http://eu.wiley.com/WileyCDA/WileyTitle/productCd-0470092408.html
PS: Cosa vuol dire "possiblita' di non impazzire per generarsi delle dll 
e magari avere la possibilita' di scriptarlo"?


 intendo : [...]
Personalmente ho trovato molte buone dritte in «C++ Coding Standards» di
Sutter e Alexandrescu.
Per andarti a riprendere le perdite di memoria potresti abituarti a usare
valgrind o GDB (su Linux; ci sono di sicuro equivalenti su Windows).
Per quanto riguarda i puntatori intelligenti, il mio primo suggerimento
sarebbe «non usare i puntatori», a meno che sia assolutamente necessario;
un uso eccessivo di puntatori può essere un indice della sindrome del C+,
dove programmi in C++ usando la stessa mentalità del C (la vecchia battuta
sul nome C++, invece che ++C, è che il linguaggio è stato incrementato, ma
viene ancora usato come il C).
Dài quindi un'occhiata ad auto_ptr, che distrugge gli oggetti che punta
quando viene distrutto, o shared_ptr delle librerie boost, che distrugge un
oggetto quando l'ultimo puntatore (dei molti che puoi aver generato) viene
distrutto.
Tieni sempre però presente che solo usare shared_ptr non ti immunizza
completamente dalle perdite di memoria; se per esempio un oggetto si tiene
ancora stretto un puntatore (per esempio in una lista di puntatori che non
viene più usata), l'oggetto puntato viene tenuto in memoria e l'effetto
complessivo è lo stesso di una perdita.
Ciao,
-Federico

sarebbe «non usare i puntatori», a meno che sia assolutamente necessario;
Quanto č vero. Il problema vero, perň, č far capire ai programmatori
che ciň che loro considerano "assolutamente necessario", a volte
magari non lo č :-)

 sarebbe «non usare i puntatori», a meno che sia assolutamente necessario;
Come faresti una struttura ad albero, senza puntatori?
E.

?manu* ha scritto:

Probabilmente Federico intendeva una (o piu') delle seguenti cose:
* Non usare l'aritmetica dei puntatori. Per esempio, per accedere agli 
elementi di un array si possono usare indici o puntatori; meglio usare 
gli indici.
* Per oggetti di piccole dimensioni (meno di 1 KB) e' meglio allocarli 
sullo stack che sullo heap. Di conseguenza, e' meglio usare direttamente 
gli oggetti che i puntatori agli oggetti allocati.
* Passando un oggetto a una funzione, e' meglio passarlo per riferimento 
che per indirizzo.
Tuttavia, per rispondere alla domanda, ti faccio notare che in alcuni 
linguaggi non esistono puntatori ne' riferimenti, come nei vecchi 
Fortran e Basic. In tali linguaggi pero' si riesce comunque a creare 
strutture ad albero, nel seguente modo.
- I nodi dell'albero sono memorizzati come record di un array.
- Un riferimento a un nodo e' implementato come indice del nodo nell'array.
Questa soluzione puo' essere addirittura piu' efficiente dell'uso dei 
puntatori e quindi consigliabile anche in linguaggi che consentono l'uso 
dei puntatori.
Il difetto principale di questa soluzione sta nel fatto che l'array deve 
essere dimensionato in modo da avere abbastanza celle per tutti i nodi 
che si intende allocare, o alternativamente effettuare costose 
riallocazioni dell'array.
I vantaggi sono i seguenti:
* ogni nodo non ha overhead di spazio e tempo di allocazione;
* la memoria e' piu' locale e quindi si usa meglio la cache;
* nel caso in cui l'array contiene sicuramente meno di 65536 elementi, 
gli indici possono essere a 16 bit invece che a 32 o 64 bit, e quindi si 
risparmia memoria.
-- 
     Carlo Milanesi
 
http://digilander.libero.it/carlmila

 - I nodi dell'albero sono memorizzati come record di un array.
Sì, certo, questa è una soluzione valida, se la struttura è statica (non 
aggiungo o tolgo elementi). Ma se la struttura è dinamica dovresti 
implementarti un gestore della memoria... allora è probabile che sia più 
efficiente utilizzare il gestore dello heap.
In effetti mi pare di capire che l'utilizzo dei puntatori è strettamente 
legato all'allocazione dinamica.
E.

 E.

 legato all'allocazione dinamica.
Oggi si' in C++, o almeno dovrebbe esserlo, ma in passato (e anche oggi 
in linguaggio C) si tendeva a usare i puntatori anche per manipolare 
stringhe e array statici.
Inoltre un uso che si fa dei puntatori e' il passaggio di un parametro 
che puo' avere valore "nullo".


Fortran e Basic.
La mia perplessitŕ č come si potrebbe gestire l'espansione dinamica di
un array che contenga i nodi di un albero binario, in quei linguaggi,
senza poter allocare quando serve. Mi sembra anche di ricordare che in
Basic per riallocare un array bastava usare il Dim con una dimensione
differente.

 differente.
Non vedo la difficolta'.
Dato che questo e' un newsgroup sul C++, qui non parlero' di altri 
linguaggi.
Se il massimo numero di nodi e' noto in fase di compilazione, basta 
allocare staticamente un array di tale dimensione.
Altrimenti, se il massimo numero di nodi e' noto all'inizio dell'uso di 
tale struttura dati, si usa un vector e si chiama reserve per allocare 
la dimensione richiesta, oppure si alloca un array dinamico con malloc o 
new[].
Se infine il massimo numero di nodi non e' noto neanche all'inizio 
dell'uso della struttura dati, il vector o l'array dinamico vengono 
creati con una capacita' iniziale piccola e poi riallocati 
esponenzialmente quando necessario.


 per cui è molto meno error prone).
ma non puo' essere NULL. Personalmente quando scelgo una signature di
un metodo, scelgo spesso di passare by pointer tutti quei parametri
che potrebbero essere nulli e per reference tutti quei parametri che
per la semantica stessa del metodo non possono mai essere nulli.
Nr.


Aggiungo solo che in C++ questo sistema può essere reso del tutto 
trasparente. Ridefinisci new/delete per il tuo oggetto in modo che vada 
a scegliersi il suo posto nell'array automaticamente.
E.

linguaggi.
Veramente gli altri linguaggi li hai tirati in ballo tu :-) e comunque
non parlavo di difficoltà, parlavo di perplessità. L'allocazione
dinamica di memoria deve essere supportata in qualche modo, dai
linguaggi che citi, per poter implementare un albero binario...
Comunque, prescindendo dai linguaggi, le soluzione che proponi non mi
sembrano più efficienti, anzi...

 vada a scegliersi il suo posto nell'array automaticamente.
Io sono *molto* scettico sugli usi brillanti di new e delete. Ci si va 
spesso a impestare, per cui lo farei solo se è quasi indispensabile, 
non certo se è solo 'elegante'.
-- 
Let us change our traditional attitude to the construction of programs. 
Instead of imagining that our main task is to instruct a computer what 
to do, let us concentrate rather on explaining to human beings what we 
want a computer to do. (Donald Knuth)

 certo se è solo 'elegante'.
Non è "solo" elegante. Ti permette una netta separazione dei problemi. 
Il codice che gestisce la struttura ad albero viene scritto senza sapere 
come avviene l'allocazione. Il codice che gestisce l'allocazione può 
quindi essere fatto a parte senza interferenze. Mi sembra chiaro che 
questo comporta molti vantaggi.
E.

 sembra chiaro che questo comporta molti vantaggi.
Mi fai vedere un codice di esempio e ne parliamo? Così in astratto è 
complesso. Mi vengono in mente molti piccoli problemi che una soluzione 
del genere può avere.
-- 
A list is only as strong as its weakest link.
Donald Knuth

 del genere può avere.
Quanto mi dai? ;-)
Comunque, supponi di avere un codice che utilizza un oggetto Obj in 
contenitori quali vector<Obj> e tree<Obj>... Supponi ora che Obj, nello 
sviluppo, diventa troppo grosso per essere spostato per copia e si 
decide di utilizzare una allocazione statica. Allora ridefinisci Obj 
come uno smart_pointer alla vera rappresentazione, che va in 
ObjInternal. Un abbozzo potrebbe essere:
class ObjInternal {
public:
	void func1();
	void func2();
	// bla bla...
 
};
class Obj {
private:
	static ObjInternal pool[1024];
	typedef short unsigned offsetType;
	static offsetType firstFree;
	offsetType offset;
public:
	Obj() {
		offset=firstFree;
		// aggiorno firstFree al prossimo libero
		// ad esempio, brutalmente
		firstFree=*(offsetType *) &pool[firstFree];		
		// ma sarebbe meglio usare una union, per questo.
		// poi creo un oggetto ObjInternal "in place"
		// alla posizione &pool[offset]
		...
	}
 
	// qui duplico tutte le funzioni di ObjInternal
	void func1() {
		pool[offset].func1();
	}
	// etc
};
E.

 differente.
esatto. ReDim ... Preserve
La clausola preserve è essenziale nel caso che si debba 
conservare i valori esistenti oltre a espandere ... o a 
contrarre. Ad es. io la usavo spesso (con array non giganteschi) 
preallocando il massimo stimabile (se possibile), facendo i 
lavori senza estendere mai, e a compito concluso, usando 
Preserve per ritagliare la parte utile dell'array e restituire 
tutto il margine preallocato. Veloce è veloce in quanto fa il 
minimo numero di riallocazioni possibili, ma ovviamente con 
array enormi non se puede fare.
Per inciso esistevano anche funzioni suicide tipo
VarPtr, StrPtr, AddressOf, che talvolta bisognava usare per 
forza per comunicare con certe API di windows (ad es. AddressOf 
con le funz. callback).
Ciao
Soviet

 ObjInternal. Un abbozzo potrebbe essere:
Si, ma non hai mica ridefinito operator new!
Questo io contestavo. BTW, per fare quello che vuoi tu, il modo 
standard non è inventarsi magia da mettere nel costruttore, ma usare un 
allocatore custom.
In pratica ti scrivi un allocatore e parametrizzi il tuo oggetto per 
fare uso di quell'allocatore. Eventualmente con default, eh.
Dopo di che 9/10 è complicato fare meglio degli allocatori standard del 
C++ se non in casi *molto* particolari (per esempio un fottio di 
piccoli oggetti, l'allocatore standard è fatto per essere piuttosto 
efficiente su *tutti* i tipi di oggetto, il che vuole dire che per 
esempio appoggiandosi a malloc et similia non alloca mai meno di x byte 
-- potrebbe essere 16, IIRC).
Quello che io avevo pensato dalla tua cosa di cui sopra era che volessi 
fare un albero i cui nodi 'sapevano' dove piazzarsi e IMHO in questo 
caso non vale la candela la complicazione. Il fatto che 'in generale' 
possa essere necessario scrivere roba particolare per rendere più 
efficienti le allocazioni non lo metto in dubbio.
-- 
Software development is technical activity conducted by human beings.
Niklaus Wirth

 potrebbe essere 16, IIRC).
La gestione della memoria proposta da Carlo Milanesi è più veloce 
dell'allocazione standard. Ma si applica solo quando tutti gli oggetti 
da allocare hanno la stessa dimensione.
E.

 oggetti da allocare hanno la stessa dimensione.
Certo.
-- 
Programming is usually taught by examples.
Niklaus Wirth

 sembrano più efficienti, anzi...
I miei test mi dicono che si possono ottenere miglioramenti di velocita' 
dal 10% all'80%, sia per l'inserimento dei dati che per la ricerca. Se 
interessa, posso postare un po' di codice.


I miei test mi dicono che si possono ottenere miglioramenti di velocita' 
Ma questo è abbastanza ovvio. Mi pare che il discorso fosse incentrato
su un concetto di "efficienza" generale.

 sembra in ogni caso un modo efficiente di lavorare. 
Eppure fino al 1990 (e anche dopo) sono state scritte miliardi di righe 
di codice Fortran che non poteva farne uso, in quanto fino al Fortran 90 
il linguaggio non supportava ne' puntatori ne' allocazione dinamica. 
Eppure suppongo che almeno qualcuno dei programmi scritti in Fortran 
abbia implementato una struttura ad albero.
Comunque sono d'accordo che oggigiorno e' meglio usare l'allocazione 
dinamica. Tuttavia e' inefficiente allocare separatamente ogni nodo 
dell'albero. E' piu' efficiente allocarli a blocchi, soprattutto se tali 
nodi contengono pochi dati.
P.S. Forse c'e' un malinteso terminologico. Io uso la parola 
"efficienza" per il software, e la parola "produttivita'" per il 
programmatore.


 intendo :
C++ Manuale di Stile - Carlo Pescio

abbia implementato una struttura ad albero.
Vedi sopra: se il linguaggio non ti mette a disposizione dei
meccanismi per allocare nuova memoria quando serve, č MOLTO difficile
che tu riesca a implementare un albero binario in maniera efficiente.
Io uso la parola "efficienza" nella sua accezione piů semplice, ovvero
considerando l'ottimizzazione sia dello spazio che del tempo.

 
No.
Ad esempio nel caso di array si creano di dimensione più grande 
possibile, o almeno è quello che ho visto fare.
Manlio Perillo

 considerando l'ottimizzazione sia dello spazio che del tempo.
Ti ho gia' detto che non e' affatto difficile implementare una struttura 
ad albero efficiente usando un array. Io l'ho fatto in C++ e se vuoi ti 
mando del codice d'esempio. Ma in C, in Basic e in Fortran sarebbe 
altrettanto facile.
Naturalmente, quando dico che "non e' difficile", intendo dire che "non 
e' piu' difficile che usando l'allocazione dinamica dei nodi e i 
puntatori". L'implementazione di una struttura ad albero efficiente non 
e' mai semplice.


possibile, o almeno č quello che ho visto fare.
Inutile, non riesco a farmi capire. Creare un array di dimensione piů
grande possibile NON E' semplicemente possibile in moltissimi casi,
per il semplice fatto che la dimensione piů grande possibile NON č
conosciuta. E' piů chiaro cosě? Poi, non capisco il tuo "No" a cosa
sia riferito. Il Fortran NON gestisce l'allocazione dinamica, quindi
chi vuol implementare allocazione dinamica deve, per esempio,
interfacciarsi al C.

e' mai semplice.
Come già detto, non si tratta di "difficoltà", si tratta di
efficienza. Tutto ciò che descrivi NON E' efficiente.
Chiudo qui la discussione da parte mia, per non sentirimi ripetere per
la quarta volte che "se conosci la dimensione massima....", "non è
difficile...", ecc... ecc...

 al C.
Ti sto solo riferendo a quello che ho visto fare :).
Il mio No era riferito al fatto che, nella mia piccola esperienza con il 
Fortran 77, non ho mai visto allocare la memoria dinamicamente.
Manlio Perillo

 Inutile, non riesco a farmi capire. 
Dimenticavo di indicare che il codice che ho visto erano solo programmmi 
di calcolo numerico abbastanza semplici.
Manlio Perillo

 capire cosa stai facendo?
In effetti, da quello che scrivi, credo proprio che non ti convinceresti 
neanche vedendo del codice.


 cui e' possibile allocare staticamente i dati.
Dal Fortran 90 esistono gli array di tipo Allocatable.
-- 
Andrea Francia
http://www.andreafrancia.it/

 
Scusate ma penso di aver messo un commento inutile. Ho riletto meglio il 
thread, era già chiaro che la limitazione degli array a dimensione fissa 
è rimasta solo fino al Fortran 77.
Comunque non sono d'accordo nel dire che ci sono "pochissimi" casi in 
cui puoi allocare staticamente i dati.
Una persona non puo' fare una statistica oggettiva, a seconda 
dell'ambiente dove lavori vedi che l'allocazione statica e sufficiente 
pochissime volte o che sufficiente molte volte.
Comunque confermo anch'io che ho visto gente usare il Fortran 77 per 
fare calcoli sismici senza lamentarsi mai di dover allocare staticamente 
gli array.
Certo in altri ambiti l'impossibilità di allocare dinamicamente la 
memoria è inacettabile. Assolutamente è il caso di dire che *dipende* 
dal quello che devi andare a fare.


 *EFFICIENTE*.
Non sono d'accordo, secondo me *dipende*.
L'allocazione statica costa meno di quella dinamica.
Se conosci quanto spazio occuperà l'albero allora l'allocazione dentro 
un array è più efficiente (in termini di velocità).
Se non conosci quanto spazio occuperà l'albero allora l'allocazione 
dentro un'array potrebbe diventare un problema quando devi riallocare 
l'array per aumentare lo spazio allocato.
In ogni caso credo che sia piu' facile da scrivere l'allocazione sull'heap.
Comunque dipende dal problema da risolvere.


 interfacciarsi al C.
La dimensione massima dell'array č la dimensione massima della memoria 
disponibile. Nel 1990 poteva essere del tutto sensato dichiarare un 
array da 64K e utilizzarlo per l'allocazione "dinamica". Ti ricordo che 
il *linguaggio* C non prevede l'allocazione dinamica, che viene invece 
definita nella libreria standard. In codice C molto vecchio 
l'allocazione dinamica (malloc, per intenderci) non veniva usata, ma si 
utilizzavano gestori della memoria fatti a mano, su array statici.
E.

 staticamente gli array.
Non mi stupisce. Il calcolo numerico è un campo *abbastanza* peculiare.
In altri campi non avere la memoria dinamica è *estremamente* scomodo.
Tanto è vero che è una feature aggiunta abbastanza rapidamente a quasi 
tutti i linguaggi 'general purpose'.
-- 
	Never trust a computer you can't throw out a window.
Steve Wozniak

 concetto?
Credo che non vi stiate capendo.
L'allocazione dinamica è comoda? Si. Comodissima. Su questo siamo tutti 
d'accordo.
Se ne può fare a meno? Dipende. Non mi è mai capitato di scrivere un 
programma *intero* che non facesse *mai* uso di memoria dinamica. 
Tuttavia tipicamente quando ho scritto roba numerica mi è stato molto 
frequente nei nuclei di calcolo *non* allocare memoria dinamicamente 
(principalmente perchè in quelle situazioni una chiamata a malloc ti 
accoppa le performance).
Per cui suppongo dipenda dal *tipo* di programma che stai scrivendo.
Ma nei casi in cui se ne deve fare a meno (il linguaggio non lo 
supporta o hai requisiti di costo non trascurabili) ci si arrangia. 
Fortunatamente in quei casi riesci anche a farne a meno per come sono 
fatti i problemi (per lo meno quelli che io ho affontato).
-- 
Computers are useless. They can only give you answers.
Pablo Picasso

 concetto?
Io direi che molto spesso è possibile effettuare una stima di quant'è la 
massima dimensione dei dati. Ad esempio se stai lavorando ad un 
videogame, e vuoi usare un albero per tenerti i poligoni di una scena,
da analisi prestazionali saprai quante migliaia di nodi potrai avere 
massimo.
Analisi di questo genere devono comunque essere fatte per avere una 
stima del costo computazionale degli algoritmi che applichi sulle 
strutture dati e per sapere se stai sforando la memoria che hai 
disponibile nella macchina.
Inoltre, puoi sempre tenere sotto controllo la quantità dei nodi 
dell'albero in fase di debug e tunare la dimensione statica dell'array
di conseguenza.
Chiaro poi che l'allocazione dinamica ti semplifica la vita di molto.
ciao

 e' mai semplice.
Beh esistono algoritmi ben collaudati, diciamo che è un argomento ormai 
abbastanza maturo.