Newsgroup: it.comp.lang.c++
Titolo: separare distruttore da deallocatore

In una classe vorrei separare la fase di distruzione (e rilascio delle
risorse) dalla fase di deallocazione (rilascio della memoria).
Pensavo di fare qualcosa del genere:
class T {
  public:
  T() {
     cout<<"T constructed "<<this<<endl;
  }
  ~T() {
     cout<<"T destructed "<<this<<endl;
  }
  void destroy() {
     this->~T();
  }
  void dealloc() {
    delete reinterpret_cast<char *>(this);
  }
};
main() {
  T* t = new T;
  t->destroy();
  t->dealloc();
}
Sembra funzionare. Ma ho qualche garanzia?
E.

 E.

 E in effetti non sarebbe male una sicurezza aggiuntiva di questo genere
1) Nell'esempio sopra, il distruttore di T (T::~T()) viene chiamato una 
sola volta.
2) Il distruttore puo' essere chiamato piu' volte, e questo non fa danni 
se si prevede la possibilita' di tale evenienza. Per esempio, se la 
classe T ha un membro "m" di tipo puntatore, il seguente distruttore:
T::~T() { delete m; }
deve essere chiamato una sola volta. Ma il seguente distruttore:
T::~T() { delete m; m = 0; }
puo' essere chiamato quante volte si vuole.
-- 
Carlo Milanesi
http://carlomilanesi.wordpress.com/

 Sembra funzionare. Ma ho qualche garanzia?
Non sono un esperto di aspetti di questo tipo, per cui potrei sbagliare, 
ma temo che in realt� non funzioni.
Per prima cosa, ma qui potrei molto facilmente sbagliare, non sono 
convinto che il reinterpret_cast permetta la completa deallocazione di 
tutta la memoria occupata dall'oggetto.
Quello che mi pare per� pericoloso � che lo standard dice (12.4.14):
<< Once a destructor is invoked for an object, the object no longer 
exists >>
Siccome non fa riferimento al tipo di chiamata al distruttore (esplicita 
o implicita) direi che si riferisca ad entrambe le modalit�.
A questo punto mi pare che la chiamata t->dealloc() risulti essere fatta 
su un oggetto che, in base a quanto scritto prima, non pare esistere pi� 
e questo, se funziona, � per puro caso.
Direi che se veramente hai questa necessit� di separare distruzione da 
deallocazione il metodo corretto sia quello di fare altrettanto in fase 
di costruzione.
Vedi:
http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.14
HTH
-- 
http://www.econet-project.eu

 se si prevede la possibilita' di tale evenienza.
No, è undefined behaviour. Il distruttore può essere chiamato una volta 
sola, perché una volta chiamato l'oggetto non esiste più.
Ciao.
-- 
http://www.econet-project.eu

 Sembra funzionare. Ma ho qualche garanzia?
No. IMHO stai facendo una cosa terribile.
Quello che vuoi fare puoi farlo in modo molto piu' pulito.
Allora, veniamo ai problemi. E' lecito chiamare esplicitamente il
distruttore (cosa che a me non fa impazzire, ma tant'e'). 
Una volta che tu hai chiamato il distruttore (dentro destroy) quando
chiami dealloc in seguito stai facendo una cosa formalmente non valida
(chiami un metodo di un oggetto distrutto). 
A quel punto sarebbe anche illecito IMHO chiamare semplicemente delete
(in quanto chiamerebbe implicitamente il distruttore e di conseguenza ti
frega).
Io vedo essenzialmente due soluzioni: la prima e' metterti nel mondo
delle new di piazzamento. A questo punto tu naturalmente hai separato le
due cose. Prima ti allochi la memoria e ci costruisci dentro l'oggetto
(con il placement new). Poi chiami il distruttore esplicito quando vuoi
distruggere l'oggetto (*senza* bisogno del destroy). E senza bisogno del
deallocate farai delete del buffer.
L'altra e' usare un po' di paradigma raii per fare la cosa,
eventualmente usando tecniche prese dal primo orpello.
Quindi avresti un oggetto X che:
1. alloca un buffer di char
2. ci crea dentro un oggetto Y (che ha i tuoi dati "veri")
   e gli delega il delegabile, controllando che sia vivo.
3. puo' chiamarne il distruttore (nel *suo* medodo destroy,
   ma senza morire egli stesso). A questo punto deve evitare
   di delegare, se no sono dolori di undefined behaviour
4. puo' chiamare il deallocatore, che dealloca il buffer.
Nel suo distruttore avra' chiamate (variamente protette per non essere
ridondanti) a quei metodi lassu'. In questo modo se exception safe: se
qualcosa scoppia, rilasci comunque risorse e memoria. Questo nel primo
modello ovviamente non sarebbe avvenuto e gesubambino avrebbe pianto.
-- 
-riko
http://www.enrico-franchi.org/
http://rik0-techtemple.blogspot.com/

 Quello che vuoi fare puoi farlo in modo molto piu' pulito.
Aggiungo... vogliamo meglio elaborare sul motivo per cui vuoi fare
questa cosa? Forse ci sono sistemi piu' semplici che ti danno il
comportamento richiesto senza i cazzi allegati (non e' C++ di tutti i
giorni, ecco).


 sola, perché una volta chiamato l'oggetto non esiste più.
Non mi pare. Il distruttore non rende l'oggetto non esistente, in quanto 
e' una normale funzione membro che tipicamente distrugge le risorse 
allocate dall'oggetto ma non l'oggetto stesso.
Si chiama distruttore perche' tale funzione viene chiamata 
automaticamente dal supporto run-time quando l'oggetto stesso viene 
distrutto per qualsiasi motivo. Pero' e' anche possibile chiamarla 
esplicitamente, e poi continuare a usare l'oggetto su cui e' stata chiamata.
Hai riferimenti a testi che sostengono la tua affermazione?


 Hai riferimenti a testi che sostengono la tua affermazione?
Dallo standard C++:
"""
Once a destructor is invoked for an object, the object no longer exists;
the behavior is undefined if the destructor is invoked for an object
whose lifetime has ended (3.8). [Example: if the destructor for an auto-
matic object is explicitly invoked, and the block is subsequently left
in a manner that would ordinarily invoke implicit destruction of the
object, the behavior is undefined. ]
"""
Sez 12.4, par 14, pg 193


 chiamata.
non lo sapevo. Salvo le funzioni membro (che cmq sono codice 
e di per sé esistono) e variabili statiche, pensavo che ogni 
variabile di istanza perdesse di legittimità.
Che poi la cosa si veda subito, pensavo, dipendeva dal fatto 
che la zona di memoria fosse stata già riusata o meno. E cmq 
l'errore nel primo caso potrebbe vedersi anche altrove, con 
logiche strane di comportamento (es valori nello stack 
alterato di altre istanze o altre funzioni).
Cmq questa era ciò che pensavo, non essendone sicuro
ciao
Soviet


 Sez 12.4, par 14, pg 193
Allora mi scuso per la mia inesattezza. D'altra parte io non ho mai 
letto lo standard del linguaggio. Come riferimento del linguaggio uso il 
testo di Stroustrup, che non mi pareva citasse tale specificazione.
La mia convinzione era d'altra parte motivata dal fatto di aver visto 
del codice scritto da altri come il seguente, dove "m" e' una variabile 
membro di tipo puntatore:
   T::~T() { delete m; m = 0; }
Se e' errato usare l'oggetto dopo averne chiamato il distruttore, allora 
annullare il puntatore membro e' inutile, dato che non verra' mai piu' 
usato. Pensavo che fosse una tutela per il caso in cui l'oggetto fosse 
usato dopo averne chiamato il distruttore (per esempio, chiamando 
un'altra volta il distruttore).


 sua, non ï¿œ ancora stata reclamata da nessuno.
Vedi il mio altro post.
Quello che posso aggiungere qui e' che con tutti i compilatori che ho 
usato, anche senza attivare le ottimizzazioni, il seguente programma:
#include <iostream>
using namespace std;
class A {
public:
	int m;
};
int main() {
   A a;
   a.m = 123;
   cout << a.m << endl;
}
genera lo stesso codice oggetto e lo stesso codice eseguibile che si 
ottiene sostituendo il corpo del main con il seguente codice:
   int a;
   a = 123;
   cout << a << endl;
D'altra parte se aggiungo alla classe A la seguente riga:
   ~A() { }
i due programmi risultano differenti sia con ottimizzazioni che senza 
ottimizzazioni.
Ne desumo un oggetto appartenente a una classe senza distruttore, quando 
viene distrutto, si comporta come un oggetto primitivo; mentre un 
oggetto appartenente a una classe con un distruttore vuoto fa qualcosa 
di diverso.


 un'altra volta il distruttore).
Quella e' una cosa diversa. L'abitudine di settare a 0 i puntatori
deallocati e' vecchia (io lo facevo anche in C) e ci sono diversi motivi
per farlo. Da un punto di vista "chiappe al muro" riliberare un
puntatore a 0 non e' un errore. Ma si puo' anche ritenere che la doppia
free indica qualcosa che non va e sarebbe meglio che scoppiasse un bel
baco.
Per il resto un puntatore a 0 rende piu' facile chiappare i bachi. Si
capisce subito che se lo stai dereferenziando di nuovo e' un errore,
mentre se non lo facessi dovresti andare a ricostruire che in effetti
quell'area di memoria era stata effettivamente deallocata.
Tipicamente c'e' una scuola che dice che e' meglio non farlo "di
default" e una che dice di farlo. Gli argomenti di entrambe le parti
sono sensati, in effetti.
<
http://stackoverflow.com/questions/1879550/should-one-really-set-pointe
rs-to-null-after-freeing-them>


 baco.
Era ovviamente un bel segfault.


 un'altra volta il distruttore).
O magari ci son modi di ingannare il compilatore.
Esempio : si può prendere l'indirizzo di un distruttore ? 
Non dovrebbe potersi fare, ma se fosse, allora difficilmente 
il compilatore potrebbe tracciare e segnalare chiamate 
multiple, via puntatore.
Però essendo funzioni un po' speciali non sono per niente 
sicuro che si possa prenderne l'indirizzo impunemente.
ciao
Soviet

 sicuro che si possa prenderne l'indirizzo impunemente.
Vado a memoria, ma mi sa che non si puo' prendere l'indirizzo del
distruttore da standard. Poi magari funziona.


 dichiarazione, e quindi distrutta all'inverso.
Lo standard (12.6.2 5) lo precisa: i membri vengono inizializzati in 
base all'ordine con i quali sono dichiarati nella definizione della 
classe, e distrutti in ordine inverso.
Ciao
-- 
http://www.econet-project.eu