Newsgroup: it.comp.lang.c++
Titolo: Distruttore standard vs Delete

Ciao a tutti.
Questo e' cio' che ho capito della differenza tra distruttore standard
e delete:
--------------------------------
DISTRUTTORE vs DELETE
--------------------------------
- DISTRUTTORE STANDARD:
viene richiamato automaticamente per deallocare **variabili**: la
visibilità è importante in quanto serve a delineare il tempo di vita
della variabile. Quando la variabile cessa di essere visibile, questa
viene deallocata automaticamente.
- DELETE:
viene richiamato dall'utente per deallocare la variabile referenziata
dal **puntatore**: essendo una funzione richiamata dall'utente questa
può essere eseguita in qualunque momento, con rischi di segmentation
fault.
---------------
PROBLEMA:
---------------
Ora il problema è: perché dovrei gestire puntatori (e quindi i
delete) quando il distruttore standard e' un metodo molto più safe?
So che lavorare con i puntatori permette un minor overhead, ma questo
solo per il passaggio di parametri.
Vale lo stesso anche per le variabili mantenute in memoria?
-------------
ESEMPIO:
-------------
Ad esempio, conviene più:
class X {
   public:
      X() {
         _s = string("prova");
      }
   private:
      string _s
};
oppure:
class X {
   public:
      X() {
         _s = new string("prova");
      }
      ~X() {
         delete _s;
      }
   private:
      const string* _s
};
e poi, conviene più:
Game createGame() {
        return Game();
}
oppure:
const Game* createGame() {
        return new Game();
}
---------------------------------------------------------
Grazie a chiunque risolva questo mio dubbio!

 delete) quando il distruttore standard e' un metodo molto più safe?
Prova a implementare una lista senza puntatori.
Ciao,
Nicola

Intendo dire: quando non esiste una esigenza esplicita di puntatori
(come l'esempio che hai fatto), quando bisogna usarli e quando no,
sfruttando quindi il distruttore di default?

 }
La seconda forma è pericolosa.
I puntatori vanno usati ogni qual volta non esistono alternative più 
sicure e semplici.
Kiuhnm

 sicure e semplici.
Quindi sarebbe meglio utilizzare variabili lì dove possibile?
Ma in questo modo non esiste il rischio di introdurre overhead per il
passaggio di intere variabili tra funzioni?
Sempre con riferimento al libro "Design Patterns", in qualunque pattern
vengono sempre utilizzati puntatori, da cui il mio dubbio...

 }
La seconda. La lifetime dell'oggetto, in questo caso, non è predeterminata.

 vengono sempre utilizzati puntatori, da cui il mio dubbio...
Perché i puntatori permettono il polimorfismo.
Se una funzione accetta puntatori a una classe base, allora accetta 
anche puntatori alle classi derivate pubblicamente (IS-A).
Passando invece oggetti si incorre nello slicing.
Considera il seg. codice:
#include <iostream>
class A
{
public:
	virtual ~A() {}
};
class B : public A
{
	int b;
public:
	virtual ~B() {}
	// ROTFL!
	friend int main();
	friend int f(A *a);
	friend int g(A a);
	friend int e(A a);
};
int f(A *a)
{
	B *b = dynamic_cast<B*>(a);
	return b ? b->b : 0;
}
int g(A a)
{
	B *b = dynamic_cast<B*>(&a);
	return b ? b->b : 0;
}
int e(A a)
{
	B *b = static_cast<B*>(&a);
	return b->b;
}
int main()
{
	A a;
	B b;
	b.b = 3;
	std::cout << f(&a) << " " << f(&b) << std::endl;
	std::cout << g(a) << " " << g(b) << std::endl;
	std::cout << e(a) << " " << e(b) << std::endl;
}
Kiuhnm

 La seconda. La lifetime dell'oggetto, in questo caso, non è predeterminata.
Che intendi con "non è predeterminata"?
Considera questo frammento di codice:
Game createGame() {
         return Game();
}
main() {
   Game game;
   game = createGame();
   /* Utilizza game ... */
   /* Game over... */
   game = createGame();
}
Il distruttore di game viene richiamato o no? Perché se viene
richiamato mi sembra migliore la prima ipotesi rispetto alla seconda
che mi hai suggerito.
Con la seconda ipotesi avrei:
Game* createGame() {
         return new Game();
}
main() {
   Game* game;
   game = createGame();
   /* Utilizza game ... */
   /* Game over... */
   delete game;
   game = createGame();
}
Cosa sto sbagliando?

 anche puntatori alle classi derivate pubblicamente (IS-A).
Ricordiamo, comunque, che il polimorfismo funziona prefettamente anche con i
reference. 
Max

 Cosa sto sbagliando?
Niente.
Ma nota che nel secondo caso, fatta la dovuta correzione, hai due 
costruzioni, dua allocazioni, due distruzioni e due deallocazioni. 
Sempre e comunque.
Nel primo caso hai come minimo tre costruzioni, due copie e tre 
distruzioni. E potresti avere 5 costruzioni, quattro copie, e cinque 
distruzioni (dipende dal compilatore, non lo puoi controllare).
Quindi la domanda diventa: quanto costa tutto ciò? Se la classe è 
semplice (per esempio std::complex) probabilmente conviene costruirla 
copiarla e distruggerla più volte. Probabilmente non ha metodi virtuali 
(la cui chiamata costa di più che per i metodi non virtuali). 
Probabilmente costruttori, copiatori e distruttori sono semplici e 
inline, quindi molto ottimizzabili. In pratica l'allocatore e il 
deallocatore ti costano più che non le varie chiamate.
Se invece la classe è pò complessa, le cose cambiano. Se implementi la 
classe Game è facile che dentro ci siano parecchie cose. Molta memoria. 
Crearne un game magari significa costruire uno scenario, creare dei 
personaggi etc. Farne una copia significa duplicare tutto quanto. 
Distruggerlo ha di per se un costo.
Inoltre facilmente hai il distruttore che è virtuale (magari vuoi in 
futuro derivare AutomaticGame). Insomma: il costo dell'allocatore e del 
deallocatore è minimo in confronto al costo di tutto il resto.
Come dicevo prima: se la classe è un valore, può avere senso "passarla 
per valore" (ha senso anche la frase). Ma se è un oggetto complesso che 
non rappresenta un valore, ma proprio una entità (definisci entità come 
vuoi) allora il discorso è diverso.
Certo che ci sono "valori" molto complessi e "entità" molto semplici.
Il mio consiglio è: ragiona "per valore" se sono valori. Anche 
complessi. Se hai dei REALI e MISURATI problemi di performance, passa ad 
una allocazione dinamica NEI PUNTI IN CUI SERVE.
Se sono entità, anche semplici, ragiona al contrario. E ottimizza al 
contrario. SE SERVE.

 sicure e semplici.
Vero. E a meno di ottimizzazioni (da farsi solo SE SERVONO, il che è 
verificabile solo MISURANDO l'esecuzione con un profiler).

 reference. 
Sì, dato che i reference sono un "syntactic sugar".
Kiuhnm

 Perché mai?
Perché in C++ sarebbe meglio evitare di usare "raw" ptr.
A parte questo, non c'è niente di male.
Kiuhnm


Non del tutto, dal momento che sono anche l'unico modo di esprimere
esplicitamente gli lvalue.
Ciao,
Nicola

 protetto) è un pattern classico!
Nella forma esatta scritta qui sopra e' piuttosto un anti-pattern,
perche' il possesso dell'oggetto puntato non e' espresso esplicitamente
e in presenza di eccezioni nell'espressione che contiene la chiamata a
createGame() e' possibile causare un memory leak. In questo caso
sarebbe meglio usare uno smart pointer, std::auto_ptr se il possesso e'
esclusivo, boost::shared_ptr o chi per lui se il possesso e' condiviso.
Ciao,
Nicola

 esplicitamente gli lvalue.
Fammi un esempio.
Kiuhnm


struct A {
  A & operator= (const & A);
};
Ciao,
Nicola

 };
#include <iostream>
struct A {
	int a;			// to_do: make it private :)
	A & operator= (const A& other) { a = other.a; return *this; }
};
struct B
{
	int a;			// to_do: make it private :)
	B* operator=(const B* other) { a = other->a; return this; }
};
int main()
{
	A a1, a2, a3;
	a1.a = 1;
	a2.a = 0;
	a3.a = 0;
	B b1, b2, b3;
	b1.a = 1;
	b2.a = 0;
	b3.a = 0;
	{
		using namespace std;
		cout << "a1.a = " << a1.a << endl;
		cout << "a2.a = " << a2.a << endl;
		cout << "a3.a = " << a3.a << endl;
		cout << "b1.a = " << b1.a << endl;
		cout << "b2.a = " << b2.a << endl;
		cout << "b3.a = " << b3.a << endl;
	}
	a3 = a2 = a1;
	b3 = b2 = b1;
	{
		using namespace std;
		cout << "-----------" << endl;
		cout << "a1.a = " << a1.a << endl;
		cout << "a2.a = " << a2.a << endl;
		cout << "a3.a = " << a3.a << endl;
		cout << "b1.a = " << b1.a << endl;
		cout << "b2.a = " << b2.a << endl;
		cout << "b3.a = " << b3.a << endl;
	}
}
Kiuhnm

 };
Ciao,
Nicola


Bravo Nicola. Stavo per rispondergli che il suo operatore ....
Ma hai ragione: meglio che lo scopra da se.
No pain no gain.

 Prova ad aggiungere:
Hai ragione, ho dimenticato un '&':
b3 = b2 = &b1;
Kiuhnm