2010-07-20 8 views
5

A mio parere, il seguente codice (da qualche domanda C++) dovrebbe portare a UB, ma sembra che non lo sia. Ecco il codice:Richiama esplicitamente il risultato del distruttore nel comportamento non definito qui?

#include <iostream> 
using namespace std; 
class some{ public: ~some() { cout<<"some's destructor"<<endl; } }; 
int main() { some s; s.~some(); } 

e la risposta è:

some's destructor 
some's destructor 

ho imparato modulo C++ faq lite che non dovremmo chiamare esplicitamente distruttore. Penso che dopo la chiamata esplicita al distruttore, l'oggetto s debba essere cancellato. Il programma chiama automaticamente il distruttore al termine, dovrebbe essere UB. Tuttavia, l'ho provato su g ++ e ho ottenuto lo stesso risultato della risposta di cui sopra.

È perché la classe è troppo semplice (nessuna nuova/eliminazione coinvolta)? O non è affatto UB in questo caso?

+10

L'intero punto di * comportamento non definito * è che è ** non definito **. Il fatto che "lavori" è solo una delle infinite possibilità. – ereOn

+0

Questo distruttore è troppo semplice per avere effetti dannosi. Credo che chiamare il distruttore non sia un caso particolare, ma semplicemente chiamare un metodo della classe. Ha il caso speciale di essere chiamato a destra prima di essere deallocato (da delete o scope exit). –

+0

@ereOn: Grazie. Capisco che "lavori" in g ++ non significa che non sia "indefinito". Tuttavia, la risposta online (che potrebbe non essere corretta) NON è UB, è per questo che sono confuso. – EXP0

risposta

15

Il comportamento è indefinito perché il distruttore viene richiamato due volte per lo stesso oggetto:

  • volta quando si richiama esplicitamente
  • volta quando la portata termina e la variabile automatica è distrutta

Invocare il distruttore su un oggetto la cui durata è terminata comporta un comportamento non definito per C++ 03 §12.4/6:

il comportamento è indefinito se il distruttore viene richiamato per un oggetto la cui vita si è conclusa

vita di un oggetto termina quando il suo distruttore viene chiamato per §3.8/1:

La durata di un oggetto di tipo T termina quando:

- se T è un tipo di classe con un distruttore non banale (12.4), la chiamata distruttore inizia, o

- la memoria che l'oggetto occupa viene riutilizzata o rilasciata.

Si noti che questo significa che se la classe ha un distruttore banale, il comportamento è ben definito, perché la vita di un oggetto di un tale tipo non finisce fino alla sua archiviazione viene rilasciato, che per le variabili automatiche non accade fino alla fine della funzione. Naturalmente, non so perché invocare esplicitamente il distruttore se è banale.

Che cos'è un distruttore banale? §12.4/3 dice:

un distruttore è banale se si tratta di un distruttore implicitamente dichiarata e se:

- tutte le classi base dirette della sua classe hanno distruttori banali e

- per tutti i membri di dati non statici della sua classe che sono di tipo classe (o matrice di essi), ciascuna di queste classi ha un distruttore banale.

Come altri hanno già detto, un possibile risultato di comportamento indefinito è che il programma sembra continuare a funzionare correttamente; un altro possibile risultato è il crash del programma. Tutto può succedere e non ci sono garanzie di sorta.

+0

Grazie. Non ho il documento standard e ho capito che devo pagare per averne uno. Ti dispiacerebbe fornire la definizione di "distruttore non banale"? Quello in questione è un distruttore non banale? Grazie. – EXP0

+0

@ EXP0: ho aggiunto per voi la definizione di distruttore banale. Il distruttore nella domanda non è un distruttore banale. –

+0

@JamesMcNellis, ciò che il paragrafo dice non è lo stesso di quello che interpreti. Il paragrafo dice di chiamare il distruttore di un oggetto che è stato cancellato, e la domanda chiede di chiamare il distruttore * come un normale metodo * prima di distruggerlo. Non è la stessa cosa Chiamare esplicitamente il distruttore non cancella l'oggetto. Per eliminare esplicitamente l'oggetto devi chiamare l'operatore 'delete' sull'istanza (e non essendo automatico) BTW, non ho mai sentito parlare della possibilità di chiamare esplicitamente il distruttore, come dovrebbe essere interpretato come l'operatore' ~ '. –

1

Il problema qui è che la cancellazione/deallocazione e i distruttori sono costrutti separati e indipendenti. Molto come nuovo/allocazione e costruttori. È possibile fare solo uno dei precedenti senza l'altro.

Nel caso generale, questo scenario non è utile e causa solo confusione con i valori allocati dello stack. Al di sopra della mia testa non riesco a pensare a un buon scenario in cui vorresti farlo (anche se sono sicuro che ce ne sia potenzialmente uno). Tuttavia è possibile pensare a scenari artificiali in cui ciò sarebbe legale.

class StackPointer<T> { 
    T* m_pData; 
public: 
    StackPointer(T* pData) :m_pData(pData) {} 
    ~StackPointer() { 
    delete m_pData; 
    m_pData = NULL; 
    } 
    StackPointer& operator=(T* pOther) { 
    this->~StackPointer(); 
    m_pData = pOther; 
    return this; 
    } 
}; 

Nota: non scrivere mai una classe in questo modo. Avere invece un metodo di rilascio esplicito.

+1

Il codice per 'std :: vector' (tra gli altri) mostrerà una buona utilizzare per invocazione esplicita del dtor. –

+0

@Jerry, molto vero ma non per i valori dello stack che * sembra * essere al centro della domanda dell'OP. – JaredPar

+0

Sì, con questa restrizione è molto * più * difficile trovare uno scenario ragionevole. –

1

Molto probabilmente funziona bene perché il distruttore non fa riferimento a nessuna variabile membro della classe. Se hai provato a delete una variabile all'interno del distruttore, probabilmente ti troverai nei guai quando viene chiamato automaticamente la seconda volta.

Poi di nuovo, con un comportamento indefinito, chi lo sa? :)

6

È un comportamento non definito, ma come con qualsiasi UB, una possibilità è che (più o meno) sembra funzionare, almeno per qualche definizione di lavoro.

In sostanza, l'unica volta che è necessario (o si desidera) invocare esplicitamente un distruttore è in congiunzione con il posizionamento nuovo (ovvero, si usa il posizionamento nuovo per creare un oggetto in una posizione specificata e una chiamata esplicita per distruggere quell'oggetto).

+0

-1 Ma non è indefinito. La lingua consente chiamate esplicite in questo modo perché può essere utilizzata e può essere utile. – Elemental

+3

@Elemental: una invocazione del dtor esplicito non è UB in sé, ma distruggere lo stesso oggetto due volte più sicuramente ** è ** UB. In realtà, questo è un esempio dato nello standard (§12.4/14): "Una volta che un distruttore viene invocato per un oggetto, l'oggetto non esiste più, il comportamento non è definito se il distruttore viene invocato per un oggetto la cui durata è terminata (3.8). [Esempio: se il distruttore per un oggetto automatico viene invocato esplicitamente e il blocco viene successivamente lasciato in un modo che normalmente invocherà la distruzione implicita dell'oggetto, il comportamento non è definito.] " –

0

Ciò che la funzione principale fa è riservare spazio allo stack, chiamare il costruttore di alcuni e alla fine chiamare il distruttore di alcuni. Questo succede sempre con una variabile locale, qualunque sia il codice inserito all'interno della funzione. Il tuo compilatore non rileverà che hai chiamato manualmente il distruttore.

In ogni caso non è necessario chiamare manualmente il distruttore di un oggetto, ad eccezione degli oggetti creati con posizionamento nuovo.

3

Da http://www.devx.com/tips/Tip/12684

comportamento indefinito indica che un'implementazione può funzionare anomalo quando un programma raggiunge un certo stato, che quasi senza eccezione è il risultato di un errore. Un comportamento indefinito può manifestarsi come un arresto in fase di esecuzione, uno stato di programma instabile e inaffidabile o, in rari casi, lo può passare inosservato allo.

Nel tuo caso non si blocca perché il distruttore non manipola alcun campo; in realtà, la tua classe non ha alcun membro di dati. Se lo facesse e nel corpo del distruttore lo avessi manipolato in qualsiasi modo, probabilmente otterrai un'eccezione run-time mentre chiamerai il distruttore per la seconda volta.

0

Credo che se si desidera che il proprio codice sia corretto, è sufficiente chiamare il posizionamento nuovo e riempirlo nuovamente prima di uscire. La chiamata al distruttore non è il problema, è la seconda chiamata al distruttore fatta quando si lascia l'oscilloscopio.

0

Puoi definire il comportamento indefinito che ti aspetti?Non definito non significa casuale (o catastrofico): il comportamento di un dato programma può essere ripetibile tra le invocazioni, significa solo che non puoi RIVOLGERSI su alcun comportamento particolare perché non è definito e non vi è alcuna garanzia di ciò che accadrà.

0

È un comportamento non definito. Il comportamento indefinito è la doppia chiamata del distruttore e non con la stessa chiamata del distruttore. Se si modifica il tuo esempio a:

#include <iostream> 
using namespace std; 
class some{ public: ~some() { [INSERT ANY CODE HERE] } }; 
int main() { some s; s.~some(); } 

dove [inserire un codice QUI] può essere sostituito con qualsiasi codice arbitrario. I risultati hanno effetti collaterali imprevedibili, motivo per cui è considerato non definito.

Problemi correlati