2013-12-13 6 views
5

Secondo C++ 03 12,4/12 quando un distruttore viene richiamato esplicitamenteCosa dice C++ 03 12.4/12 sulla chiamata di un distruttore di classe base esplicitamente attraverso il puntatore?

se l'oggetto non è di tipo classe del distruttore e non di una classe derivata da tipo di classe del distruttore, il programma ha undefined comportamento

così ho questo codice:

class Base {}; 
class Derived : public Base {}; 

char memory[100]; 
new(memory) Derived(); 
Base* ptr = (Base*)memory; 
ptr->~Base(); 

Qui l'oggetto è di tipo Derived e "tipo di classe del distruttore" è Base e così è l come in accordo con la formulazione standard non ci sono motivi per UB.

Così come il codice sopra rende UB secondo lo standard?

+0

derivato di solito forniscono alcuni membri? – user1810087

+0

@itwasntpete: Beh, non necessariamente. Ad esempio potresti creare una nuova classe di eccezioni che può essere catturata separatamente senza aggiungere nuovi membri. – sharptooth

+0

Ho la sensazione che questo sia UB perché 12.4/12 consente il modulo 'derivedPtr-> ~ Base()' con l'intento di supportare i distruttori virtuali. Non avere C++ 03 disponibile qui, però. – MSalters

risposta

4

Corretto, non esiste un comportamento non definito.

invece c'è potenziale UB in questo caso a seconda delle tipologie coinvolte:

Base *ptr = new Derived(); 
delete ptr; 

La ragione è che per alcuni tipi di regolazione potrebbe essere stata applicata dall'implementazione di ottenere da Derived* a Base*. Quindi senza il puntatore all'oggetto completo non è possibile liberare correttamente l'allocazione di memoria. Un distruttore virtuale garantisce che l'oggetto secondario Base fornisca informazioni sufficienti per l'implementazione da ripristinare (il meccanismo di chiamata virtuale deve essere in grado di ripristinare il puntatore Derived* per passarlo come this).

Ma nel tuo esempio la memoria non viene liberata e quindi non c'è alcuna motivazione a renderlo UB. Ovviamente è ancora una cattiva idea, dal momento che concettualmente l'oggetto Derived è in uno stato rotto. Non hai un modo legittimo per chiamare ~Derived, pari. Nel tuo esempio, anche se entrambi i tipi sono banalmente distruttibili, quindi non c'è necessario per chiamare il distruttore di entrambi.

+1

Emmm ... Regolazione?'Derived' può facilmente avere' operator new() 'implementato su un altro heap e ciò causerebbe conseguenze terribili anche senza alcun aggiustamento. – sharptooth

+0

@sharptooth: sì, buon punto. Se 'new' e' delete' non sono sovraccaricati per questi tipi, allora è ancora UB per la mia ragione. –

+0

In realtà l'esempio new-delete è comunque UB. Il tuo esempio e il mio esempio sono solo esempi di ciò che può rompersi. – sharptooth

0

per favore correggimi se sbaglio, penso che non ci sia un comportamento indefinito, ma che comunque dovrebbe essere evitato in termini di umanità (o di mantenimento). ma, considera che Derived sta creando un qualche tipo di membro, ad es. un puntatore condiviso, (che non è atipico anche per le eccezioni :). Ho provato questo codice sulla mia macchina e anche sulla codepad:

class Base { 
public: 
    boost::shared_ptr<int> x; 
}; 
class Derived : public Base { 
public: 
    boost::shared_ptr<int> y; 
}; 


int main(int argc, char *argv[]) { 
    boost::shared_ptr<int> xx(new int(0xff)); 
    boost::shared_ptr<int> yy(new int(0xaa)); 

    int memory[100]; 
    for(int i=0; i<100; i++) 
    memory[i] = 0; 
    Derived* foo = new(memory) Derived(); 
    foo->x = xx; 
    foo->y = yy; 
    (*foo->y)--; 
    Base* ptr = (Base*)foo; 

    ptr->~Base(); 

    Derived* bar = new(memory) Derived(); 

    bar->x = xx; 
    bar->y = yy; 

    foo->~Derived(); 

    return 0; 
} 

qui è che lo shared_ptr aa non viene rilasciato, e nessuno poteva garantire di non dimenticare Derivato non dovrebbe fornire alcun genere di cose.

+1

Qualcosa non rilasciato non significa UB. – sharptooth

+0

In questo caso, penso * che l'UB stia costruendo un oggetto sulla memoria occupata da un oggetto non banalmente distruttibile che non è stato distrutto. Quindi * penso * che l'UB sia la seconda chiamata a 'new (memory) Derived()' che scarabocchia sul membro 'y'. Ma anche se ho torto e non è UB, allora 'bar-> y' è nullo, quindi è il dereferenziamento. [Quell'osservazione è sul codice del codepad, ho appena notato che il codice qui è diverso.] –

1

E 'non UB, dal momento che entrambe le classi hanno distruttori banali, e quindi chiamando il distruttore ha lo stesso effetto di non chiamare il distruttore (e non chiama il distruttore non è certamente UB).

+0

Ciò vale anche in base * base = nuovo derivato(); cancella base; 'caso? – sharptooth

+0

@sharptooth: mi spingerei fino a dire di sì; infatti, penserei "Base di base; static_cast (& base) -> ~ Derived(); 'dovrebbe funzionare anche (come assurdo come sembra) perché entrambi hanno distruttori banali, il che significa che i loro distruttori non devono avere alcun effetto ... – Mehrdad

+0

Il testo dallo standard che è citato nella domanda non ha un'eccezione per distruttori banali. Questa risposta è ** non ** sul comportamento non definito, ma sulle probabili conseguenze del codice. –