2015-12-22 14 views
10

Nel seguente codice, viene dichiarato un oggetto wrapper<T> che contiene uno movable<T>, dove T è un tipo incompleto. Il distruttore di movable viene creato in modo che non possa essere istanziato senza una conoscenza completa di T, ma il distruttore di wrapper è solo inoltrato, il che significa che dovrebbe essere sufficiente se ~movable() viene istanziato al punto di definizione di ~wrapper().Perché un costruttore noexcept richiede l'istanziazione del distruttore?

#include <utility> 

template<class T> 
struct movable { 
    movable() noexcept = default; 
    ~movable() noexcept { (void) sizeof(T); } 
    movable(const movable&) noexcept = delete; 
    movable(movable &&) noexcept = default; 
}; 

template<class T> 
class wrapper { 
public: 
    movable<T> m; 
    wrapper() noexcept = default; 
    wrapper(wrapper &&) noexcept = default; 
    ~wrapper(); 
}; 

struct incomplete; 

int main() { 
    /* extern */ wrapper<incomplete> original; 
    wrapper<incomplete> copy(std::move(original)); 
} 

(Try it here)

Tuttavia, wrapper() vuole istanziare ~movable(). Ricevo che in caso di eccezione, la distruzione dei membri deve essere possibile, ma movable() e wrapper() sono entrambi senza eccezioni. È interessante notare che la funzione di costruzione delle mosse funziona correttamente (provare a decommentare la parte extern nel codice di esempio.)

Qual è la ragione di questo comportamento e c'è un modo per aggirare il problema?

+1

compilazione clang fallisce anche quando commentando la 'extern'. – interjay

+0

Hm, "un modello di classe viene istanziato se la completezza del tipo di classe potrebbe influenzare la semantica del programma" ... forse che ha qualcosa a che fare con esso? –

+0

Se una classe ha più membri e durante la costruzione viene lanciata una delle inizializzazioni, le inizializzazioni completate in precedenza devono essere annullate chiamando i distruttori dei rispettivi membri. Penso che quello che vedi sia collegato a questo. – dyp

risposta

4

Come osservato da TC,

In un costruttore non delega, il distruttore per [...] ciascun membro dati non-statico del tipo di classe è potenzialmente richiamato [...]

Per DR1424, la motivazione è chiarire che è richiesta un'implementazione per emettere un errore se un distruttore è inaccessibile dal costruttore dell'oggetto genitore "[anche se] non è possibile lanciare un'eccezione seguendo la costruzione di un determinato oggetto secondario ".

Il distruttore di movable<T> è accessibile, ma non può essere istanziata, che è dove il problema si pone come distruttore potenzialmente invocata è ODR-usato.

Ciò rende la vita più semplice per l'implementatore, in quanto è sufficiente verificare che ciascun sottoprogetto disponga di un distruttore accessibile e se necessario istantaneo e lasciarlo all'ottimizzatore per eliminare le chiamate destructor non richieste. L'alternativa sarebbe orribilmente complicata: sarebbe necessario o non richiesto un distruttore a seconda che eventuali suboggetti successivi non fossero costruttivi e sul corpo del costruttore.

L'unico modo per evitare il potenziale invocazione del distruttore sarebbe quella di utilizzare nuova collocazione, presa in consegna la gestione del ciclo di vita del sub-oggetto da soli:

#include <new> 
// ... 
template<class T> 
class wrapper { 
public: 
    std::aligned_storage_t<sizeof(movable<T>), alignof(movable<T>)> m; 
    wrapper() noexcept { new (&m) movable<T>; }; 
    wrapper(wrapper&& rhs) noexcept { new (&m) movable<T>{reinterpret_cast<movable<T>&&>(rhs.m)}; } 
    ~wrapper(); 
}; 
Problemi correlati