2009-04-10 4 views
17

Qual è la tecnica migliore per uscire da un costruttore su una condizione di errore in C++? In particolare, si tratta di un errore durante l'apertura di un file.Qual è la tecnica migliore per uscire da un costruttore in una condizione di errore in C++

Grazie per le risposte. Sto facendo un'eccezione. Ecco il codice (non so se è il modo migliore per farlo, ma è semplice)

// Test to see if file is now open; die otherwise 
if (!file.is_open()) { 
    cerr << "Failed to open file: " << m_filename << endl; 
    throw ("Failed to open file"); 
} 

pensare che mi piace di C++ è che non deve dichiarare eccezioni sollevate sulle dichiarazioni dei metodi .

+0

Suggerirei di lanciare uno std :: runtime_error, o almeno un'eccezione std :: invece di un const char *. – GManNickG

+0

possibile duplicato di [Come gestire l'errore nel costruttore in C++?] (Http://stackoverflow.com/questions/4989807/how-to-handle-failure-in-constructor-in-c) –

risposta

25

Il suggerimento migliore è probabilmente quello che dice il paraciclo. Ma leggi anche la mia nota di cautela, per favore.

See parashift FAQ 17.2

[17.2] Come posso gestire un costruttore che non riesce?

Invia un'eccezione.

I costruttori non hanno un tipo di ritorno, quindi non è possibile utilizzare i codici di restituzione . Il modo migliore per segnalare l'errore del costruttore è quindi di inviare un'eccezione a . Se non si dispone di l'opzione di utilizzo delle eccezioni, l'operazione "meno male" consiste nel mettere l'oggetto in uno stato "zombie" per impostando un bit di stato interno in modo che l'oggetto agisca come se fosse morto anche se tecnicamente è ancora vivo.

L'idea di un oggetto "zombi" ha un lotto di down-side . Devi aggiungere una funzione membro ("inspector") a e controllare questo bit "zombi" in modo che gli utenti della classe possano vedere se il loro oggetto è veramente vivo o se è uno zombi (ad esempio, un " oggetto "morto vivente", e quasi tutti gli edifici (incluso all'interno di un oggetto più grande o una serie di oggetti) è necessario controllare lo stato tramite un'istruzione if. Dovresti anche aggiungere un if alle tue altre funzioni membro: se l'oggetto è uno zombi, fai un no-op o forse qualcosa di più odioso.

In pratica la cosa "zombi" diventa piuttosto brutta. Certamente dovresti preferire le eccezioni sugli oggetti zombi, ma se non hai l'opzione di usando le eccezioni, gli oggetti zombie potrebbero essere l'alternativa "meno male" .


Una parola di cautela con generazione di eccezioni in un costruttore:

essere molto attenti, però, perché se viene generata un'eccezione in un costruttore, distruttore della classe non è chiamato. Quindi devi stare attento a distruggere gli oggetti che hai già costruito prima di lanciare l'eccezione.Gli stessi avvertimenti si applicano alla gestione delle eccezioni in generale, ma è forse un po 'meno ovvio quando si ha a che fare con un costruttore.

class B 
{ 
public: 
    B() 
    { 

    } 

    virtual ~B() 
    { 
     //called after D's constructor's exception is called 
    } 
}; 

class D : public B 
{ 
public: 
    D() 
    { 
     p = new char[1024]; 
     throw std::exception("test"); 
    } 

    ~D() 
    { 
     delete[] p; 
     //never called, so p causes a memory leak 
    } 

    char *p; 
}; 

int main(int argc, char **argv) 
{ 

    B *p; 
    try 
    { 
     p = new D(); 
    } 
    catch(...) 
    { 

    } 


    return 0; 
} 

protette/costruttori privati ​​con metodo CreateInstance:

Un altro modo per aggirare questo è quello di rendere il vostro costruttore privato o protetto e fare un metodo CreateInstance che può restituire errori.

+0

@Neil Butterworth: I Sto cercando di ricordare perché, ho già avuto problemi con una patch alla libreria CLucene, qualcosa che riguarda i tipi derivati. In qualche modo ha portato a un tipo parzialmente costruito che ha causato una pura chiamata alla funzione virtuale per arrestare il programma utilizzando CLucene. –

+0

Aggiornato con il motivo per cui può portare a molti problemi se il costruttore non si ripulisce da solo prima di chiamare l'eccezione. –

+0

Scusa - Penso che si applichino tutti allo stesso modo all'utilizzo di un ritorno da un costruttore quando il distruttore verrà chiamato su un oggetto probabilmente costruito in modo improprio, con risultati orribili. In entrambi i casi la risposta è usare oggetti autogestiti come membri della classe. –

1

Se si oppone dopo l'errore non è possibile eseguire le sue azioni - è necessario lanciare. Se è possibile, registra l'errore e modifica la logica di costruzione.

4

In generale, è necessario generare un'eccezione. L'alternativa è avere un oggetto costruito a metà correttamente che l'utente deve testare in qualche modo, che inevitabilmente non riusciranno a fare.

2

Se l'oggetto che stai costruendo non è valido a causa dell'errore, e deve essere eliminato dal chiamante, allora devi praticamente lanciare un'eccezione. Ciò consente al compilatore di eseguire la corretta deallocazione delle risorse.

(La scrittura di costruttori eccezionalmente sicuri richiede un po 'di attenzione - in breve, è necessario utilizzare gli elenchi di inizializzazione ovunque sia possibile, piuttosto che utilizzare il corpo del costruttore - ma è fondamentale se si dispone di un caso come questo, dove lanciare un'eccezione è una possibilità significativa.)

+0

Puoi ampliare un po 'il motivo per cui è fondamentale utilizzare gli elenchi di inizializzatori quando può essere generata un'eccezione? Sembra che tu stia insinuando che non sarebbe intrinsecamente sicuro da buttare fuori dal corpo del costruttore, ma non sono sicuro di capire perché. –

+0

Non intendevo dire che un lancio dal corpo non è sicuro. Quello che intendevo era che quando si butta da qualsiasi parte del ctor (corpo o elenchi di inizializzatori) i ditori saranno chiamati per tutti i campi che sono stati initalizzati. Supponiamo che il lancio avvenga durante un ctor per un membro della classe che è esso stesso un'istanza di classe. –

+0

(cnt'd) - il ctor chiamante dovrà distruggere tutti i campi inizializzati e dovrà * non * sapere per distruggere tutti i campi che non sono ancora stati inizializzati.Il compilatore assicura che ciò avvenga correttamente; è impossibile riuscire a farlo correttamente in tutti i casi se si ignora il compilatore e lo si fa da soli. –

0

C'è solo un buon modo per uscire da un costruttore che è in errore, vale a dire sollevare un'eccezione.

È davvero un errore? stai cercando di aggiungere troppo al costruttore?

Spesso le persone cercheranno di introdurre un'interazione iniziale nel costruttore, come aggiungere il nome file a un costruttore di file. Ti aspetti che apra quel file subito o stai semplicemente impostando uno stato, è diverso da file.open (nomefile), è ok se fallisce?

6

È possibile generare un'eccezione, come altri hanno già menzionato, oppure è possibile rifattorizzare il codice in modo che il costruttore non possa fallire. Se, ad esempio, stai lavorando a un progetto in cui le eccezioni sono disabilitate o disabilitate, quest'ultima è l'opzione migliore.

Per fare un costruttore che non può fallire, il refactoring del codice che potrebbe potenzialmente fallire in un metodo init(), e hanno il costruttore fare il meno lavoro possibile, e quindi richiedere a tutti gli utenti della classe per chiamare init() subito dopo costruzione. Se init() non riesce, è possibile restituire un codice di errore. Assicurati di documentarlo nella documentazione della tua classe!

Naturalmente, questo è un po 'pericoloso, dal momento che i programmatori potrebbero dimenticare di chiamare init(). Il compilatore non è in grado di imporlo, quindi fai attenzione e prova a rendere il tuo codice più veloce se non si chiama init().

+0

Un costruttore può sempre fallire. Tutto ciò che deve allocare memoria può sempre fallire, in particolare nel tipo di ambiente che è probabile non utilizzare le eccezioni. –

+0

Mi piacerebbe sapere come un costruttore che non fa nulla, o uno che inizializza solo POD può fallire. –

+0

Può non riuscire sull'assegnazione della memoria. nuovo alloca la memoria e la inizializza. In realtà, se si pre-alloca la memoria e si utilizza il nuovo posizionamento, è possibile ottenere un costruttore non di lancio. –

0

La cosa migliore da fare è lanciare un'eccezione. Questo è quello per cui sono lì, e qualsiasi tentativo di duplicare il comportamento che si ottiene è probabile che fallisca da qualche parte.

Se non è possibile utilizzare un'eccezione, per qualche motivo, utilizzare nothrow. L'esempio nello Standard, 18.4.1.1 comma 9, è:

t* p2 = new(nothrow) T; // returns 0 if it fails 

Questo è tecnicamente una forma di nuova collocazione, ma dovrebbe restituire un oggetto completamente formato o un puntatore nullo che è necessario per verificare, se non che nessuno lo farà.

Se la classe può avere un oggetto che è lì ma non correttamente inizializzato, è possibile avere un membro dati che funge da indicatore se la classe è utile o meno. Ancora una volta, nessuno controllerà quella bandiera nel codice live.

Tenere presente che, se è davvero necessario disporre di un'assegnazione che è garantita non fallire, è necessario allocare la memoria prima del tempo e utilizzare il posizionamento nuovo e rimuovere tutte le inizializzazioni che potrebbero essere lanciate in un'altra routine, che qualcuno non riuscirà a chiamare. Tutto ciò che alloca la memoria può fallire, in particolare sui sistemi più limitati che di solito non supportano le eccezioni.

In realtà, le eccezioni sono il modo migliore per andare.