Newsgroup: it.comp.lang.c++
Titolo: Restituire un reference ad un oggetto appena creato...

Salve,
mi sto da poco cimentando con lo studio del C++. Vengo da un'ottima 
preparazione in C e Java e finora trovo il C++ un linguaggio 
affascinante di grande
potenza ma di molto difficile comprensione. Mi pare di capire, 
studiandone un po' la semantica, che impararlo vermente bene richieda 
una forte esperienza sul campo.
In ogni caso cominciando a capirci qualcosa mi chiedevo questo:
quanto č corretto e sicuro restituire un reference ad un oggetto creato
localmente in un metodo?
Mi spiego meglio: per fare i primi esperimenti semplici ho cominciato a 
scrivere la classica classe Vettore3D contenente una terna di double 
privati.
A questo punto volevo eseguire l'overload dell'operatore di somma, il 
quale abbia la semantica di restituire la somma di due vettori senza 
avere side-effects sui vettori operandi. Lo trovo il modo piů logico di 
pensare l'operazione di somma al fine di non complicare eccessivamente 
le cose poi durante l'utilizzo.
Ho quindi implementato il metodo in questo modo:
Vector3D Vector3D::operator+ (const Vector3D &w){
	Vector3D temp;		
	temp.x = this->x + w.x;
	temp.y = this->y + w.y;
	temp.z = this->z + w.z;
	return temp;
}
Fatto questo ho pensato che non fosse la maniera piů efficiente per fare
la cosa (almeno a livello teorico) perchč ero convinto che la seguente 
operazione
Vector3D z = x + y;
creasse un ulteriore oggetto vettore z e ci copiasse dentro membro a 
membro l'oggetto temp creato localmente dentro la funzione operator+ e 
poi subito distrutto dopo il ritorno. Eppure facendo un po' di 
tracciamenti del codice dentro il costruttore ed il distruttore questo 
pare non avvenire. Anzi, una volta creato il vettore v l'istruzione
Vector3D z = v + v;
chiama il costruttore di default di Vector3D una sola volta e alla 
terminazione del programma avvengono due sole distruzioni, una di z e 
una di v. Questo mi fa pensare a qualche tipo di ottimizzazione da parte 
del compilatore.
In ogni caso, il punto non era questo. Ritenendo appunto inizialmente 
almeno teoricamente poco efficiente la soluzione proposta, ho pensato 
fosse possibile  la seguente forma
Vector3D& Vector3D::operator+ (const Vector3D &w){
	return *(new Vector3D(this->x + w.x,
				this->y + w.y,
				this->z + w.z));
}
In questo modo, sfruttando l'utilizzo dei reference, si ha una sola 
creazione di oggetto e nessuna copia, risultando, almeno teoricamente, 
in un codice piů efficiente. D'altra parte perň si ha che la 
deallocazione del vettore risultato sia a carico del client.
La domanda quindi č: č questa pratica sconsigliata? č invece usuale?
c'č qualche grossa falla nel mio ragionamento?
Scusate il lungo post!
Saluti e grazie
Daniele

 c'è qualche grossa falla nel mio ragionamento?
Sconsigliatissima.
1) Allocare memoria dinamica è un'operazione relativamente lenta.
2) La probabilità di commettere errori è altissima.
3) Cercare di rubare il lavoro all'ottimizzatore è immorale
    (il programmatore dovrebbe ottimizzare ad alto, non a basso livello)
4) La soluzione è Blitz (
http://www.oonumerics.org/blitz/
).
Kiuhnm

 deallocazione del vettore risultato sia a carico del client.
E come vorresti usarlo? Cosě?
   Vector3D& copy = v + v;
    ...
   delete &copy.
Argh.
Max

    delete &copy.
 >
 > Argh.
E sopratutto, se scrivo
Vector3D& copy = a + b + c;
anche ammettendo di cancellare copy con la bruttura di cui sopra, chi mi 
  cancella il temporaneo nascosto?
--*PaN!*

 4) La soluzione è Blitz (
http://www.oonumerics.org/blitz/
).
Non so cosa usi Blitz... riguardo i vettori tempo fa scrissi una 
libreria ottimizzata con la "lazy evaluation". Ottimizzava molto bene 
sopratutto nei casi in cui espressioni complesse erano semplificabili 
algoritmicamente.
Ho abbandonato comunque la cosa per due motivi:
1) L'estrema inmantenibilita' del codice (sopratutto quello compatibile 
VC6, un bagno di sangue e macro)
2) L'insensato allungamento dei tempi di compilazione (10-100 volte 
superiori) - la prima applicazione pratica (un raytracer)
Inoltre sebbene fosse migliore nei casi "di interese", quanso le 
espressioni eccedevano un un certo limite di complessita' aveva una 
caduta rovinosa (diventava *molto* peggio dell'implementazione naive).
--*PaN!*

 caduta rovinosa (diventava *molto* peggio dell'implementazione naive).
Cosa intendi esattamente per "lazy evaluation"?
Kiuhnm

 numero di copie.
Mi sono espresso male. Blitz ritarda la "generazione di codice".
Kiuhnm

 Cosa intendi esattamente per "lazy evaluation"?
Quello che hai appena descrito =)
 > Non credo che semplifichi l'espressione anche algebricamente.
Credo che l'ottimizzatore si trovi davanti a meno roba (non deve 
ottimizzare via i costruttori, le copie, i temporanei) ed e' in grado di 
lavorare piu' a fondo.
Se necessario ravano nei backups, uno dei miei test era un espressione 
del tipo "manata sulla tastiera" di prodotti vettoriali, somme e 
sottrazioni. La versione naive scriveva una quantita' di codice inutile, 
quella con la lazy evaluation semplicemente scriveva (0, 0, 0) nel 
risultato (ma non veniva gratis - lo faceva dopo una trentina di secondi).
--*PaN!*

    delete &copy.
Non è che io voglia usarlo così, ma se lo alloco dinamicamente dentro la 
funzione operator+ mi pare l'unico modo no? Mi ero appunto posto il 
problema...
ciao

  cancella il temporaneo nascosto?
infatti...avevo pensato anche a questo dopo...
ciao

 2) La probabilità di commettere errori è altissima.
Sì ma metti che devo allocare un oggetto che occupa 1MB e restituirlo 
come risultato di una funzione?
A questo punto la domanda che mi viene è: gli oggetti locali non 
allocati dinamicamente vengono mantenuti sempre nello stack?
ciao

 risultato (ma non veniva gratis - lo faceva dopo una trentina di secondi).
Beh, bello perņ :-)
Kiuhnm

 valore direttamente?
Attento alle condizioni di applicabilita' (in vs8 ad esempio dev'essere 
il ritorno deve avvenire sempre sulla stessa variabile, anche se su 
branch differenti).
Vedi:
http://msdn2.microsoft.com/en-us/ms364057(vs.80).aspx
(in particolare le figure 7 e 8 per l'esempio di condizione di cui ti 
parlavo sopra).
--
Marco

 allocati dinamicamente vengono mantenuti sempre nello stack?
void f()
{
     double g[1000000];
     for (int i = 0; i < 1000000; ++i)
         g[i] = 0;
     cout << g[rand()%1000000] << endl;
};
int main()
{
     f();
}
Kiuhnm

 Kiuhnm
0.
Questo mi fa pensare sia nell'heap, a meno di non avere uno stack frame 
da 1 Mb :-) Però vengono distrutti nell'heap anche senza la chiamata a 
delete immagino...
grazie mille!
ciao

 delete immagino...
Da me crasha. L'ottimizzatore potrebbe anche nascondere il problema. Usa 
sempre la versione debug per fare queste prove.
Kiuhnm

 sempre la versione debug per fare queste prove.
L'ho testato in modalità debug con g++ sotto linux e non crasha.
Ma quindi è nello stack non nell'heap!
saluti
ciao

 Ma quindi è nello stack non nell'heap!
Sì, dovrebbe essere nello stack.
Ma forse il g++ ottimizza... chissà...
Kiuhnm

 In meno di 300 pagine, chiarisce molti punti oscuri del C++.
Non e' un po' vecchiotto? Ero indeciso se comprarlo o meno, visto che
e' citato molto spesso. Mi domandavo quindi quanto sia ancora attuale.
Ciao,
Nicola

    return MyObj("Ottimizzami!");
E' molto semplice. Ovunque non ci sono effetti visibili, ovvero
chiamate a funzioni che effettuano I/O (*) o modifica di variabili
dichiarate volatile, il compilatore e' tenuto soltanto a comportarsi
come se facesse tutte le cose previste dallo standard. E' la cosiddetta
"as if" rule.
Ciao,
Nicola
(*) inteso in senso lato: funzioni che hanno effetto al di fuori
dell'ambito di esecuzione del programma, come in genere succede per le
primitive di sistema

 "as if" rule.
Il MSVC si è comportato in questo modo, ma non sapevo se fosse previsto 
dallo standard.
Kiuhnm

 e' citato molto spesso. Mi domandavo quindi quanto sia ancora attuale.
Ha ancora dei buoni consigli da dare. A me è stato utile attorno al 
secondo mese di studio, quindi non credo che troverai qualcosa di nuovo.
Kiuhnm