2015-10-28 7 views
63

Si prega di dare un'occhiata al seguente lancio eccezione e la cattura di:Sta rilevando un'eccezione per riferimento pericoloso?

void some_function() { 
    throw std::exception("some error message"); 
} 

int main(int argc, char **argv) { 
    try { 
     some_function(); 
    } catch (const std::exception& e) { 
     std::cerr << e.what() << std::endl; 
     exit(1); 
    } 
    return 0; 
} 

E 'sicuro di catturare l'eccezione generata per riferimento?

La mia preoccupazione è che l'eccezione e è in realtà posto sulla pila disome_function(). Ma some_function() è appena tornato, causando la distruzione di e. Quindi in realtà ora e punti a un oggetto distrutto.

La mia preoccupazione è corretta?

Qual è il modo corretto per superare l'eccezione senza copiarlo in base al valore? Devo lanciare new std::exception() così viene inserito nella memoria dinamica?

+7

nota che il costruttore di stringhe per std :: exception non è standard, questa è un'estensione MS (conforme?). http://stackoverflow.com/questions/5157206/why-does-stdexception-have-extra-constructors-in-vc – Sigismondo

+0

Quindi, come si può assegnare un messaggio all'eccezione, quindi viene stampato usando 'what()'? – SomethingSomething

+4

@SomethingQualcosa si usa una derivata di 'std :: exception' che consente di passare il messaggio al costruttore. 'std :: runtime_error' per esempio. E la convenzione di lanciare sottotipi è esattamente la ragione per cui dovresti prendere (riferimento) riferimento. – user2079303

risposta

93

è infatti sicuro - e raccomandato - a cogliere di const riferimento.

"e è effettivamente immesso sul pila di some_function()"

No, non è ...l'oggetto effettivamente gettato viene creato in un'area specificata di memoria riservata ad uso del meccanismo di gestione delle eccezioni:

[except.throw] 15.1/4: La memoria per l'oggetto eccezione è allocato in una non specificata modo, salvo quanto indicato al punto 3.7.4.1. L'oggetto di eccezione viene distrutto dopo che l'ultimo gestore attivo rimanente per l'eccezione è terminato con qualsiasi metodo diverso da rethrowing oppure l'ultimo oggetto di tipo std :: exception_ptr (18.8.5) che fa riferimento all'oggetto di eccezione viene distrutto, a seconda di quale è dopo.

Se una variabile locale è specificato throw, viene copiato ci-to se necessario (l'ottimizzatore può essere in grado di creare direttamente in quest'altro memoria). Ecco perché ...

15,1/5 Quando l'oggetto lanciato è un oggetto di classe, il costruttore selezionato per la copia-inizializzazione e il distruttore deve essere accessibile, anche se l'operazione di copia/spostamento viene eliso (12.8).


se questo non è cliccato, si forza aiuto di immaginare realizzazione vagamente simile a questo:

// implementation support variable... 
thread__local alignas(alignof(std::max_align_t)) 
    char __exception_object[EXCEPTION_OBJECT_BUFFER_SIZE]; 

void some_function() { 
    // throw std::exception("some error message"); 

    // IMPLEMENTATION PSEUDO-CODE: 
    auto&& thrown = std::exception("some error message"); 
    // copy-initialise __exception_object... 
    new (&__exception_object) decltype(thrown){ thrown }; 
    throw __type_of(thrown); 
    // as stack unwinds, _type_of value in register or another 
    // thread_local var... 
} 

int main(int argc, char **argv) 
{ 
    try { 
     some_function(); 
    } // IMPLEMENTATION: 
     // if thrown __type_of for std::exception or derived... 
     catch (const std::exception& e) { 
     // IMPLEMENTATION: 
     // e references *(std::exception*)(&__exception_object[0]); 
     ... 
    } 
} 
+0

Penso che 15.1.3 sia tanto rilevante, se non di più. Soprattutto "* Lanciare un'eccezione copia-inizializza (8.5, 12.8) un oggetto temporaneo [...] *", quindi 'e' non punta mai all'oggetto lanciato, ma a una copia - l'oggetto eccezione - che è stato usato per inizializzare l'argomento di "catch". – luk32

+1

@ luk32: Non sono convinto: la cosa importante è che la vita dell'oggetto di eccezione sopravvive allo stack che si riconnette alle dichiarazioni 'catch' rilevanti - quale relazione ha 15.1.3 su questo? Ancora, sentiti libero di revocare la risposta di Sigismodo, se non l'hai già fatto. La tua parafrasi/conclusioni di 15.1.3 è imperfetta IMHO - la creazione di oggetti di eccezione può essere eliminata, quindi non sono necessari due oggetti distinti su quali affermazioni come "non puntare mai all'oggetto lanciato, ma a una copia" possono essere fatte accuratamente. –

+1

Umm .. Ok, fammi ritrattare un po ', è altrettanto importante. Il mio punto è che, qualunque cosa venga lanciata, potrebbe essere morta all'interno del raggio di cattura. Semanticamente, la funzione è finita. Se il compilatore decide di voler eseguire elision, va bene. Il fatto è che * l'oggetto eccezione * non è ciò che si passa all'istruzione 'throw', ma un * temporaneo * creato da esso. La loro vita non è legata l'una all'altra. Ho aggiornato entrambi btw. – luk32

18

La cattura tramite riferimento const è esattamente come devono essere rilevate le eccezioni. L'oggetto eccezione non necessariamente vive "in pila". Il compilatore è responsabile della magia appropriata per farlo funzionare.

D'altra parte, il tuo esempio non può essere compilato poiché std::exception può essere solo costruito in modo predefinito o costruito su una copia. In questo caso il metodo what() restituirebbe un puntatore a una stringa vuota (stile c), che non è particolarmente utile.

Suggerisci si getta un std::runtime_error o std::logic_error a seconda dei casi, o di una classe derivata da essa:

  • logic_error quando il chiamante ha richiesto qualcosa al di fuori dei parametri di progettazione del vostro servizio.
  • runtime_error quando il chiamante ha richiesto qualcosa di ragionevole, ma fattori esterni impediscono di rispettare la richiesta.

http://en.cppreference.com/w/cpp/error/exception

22

È necessario per la cattura per riferimento, altrimenti non potrebbe ottenere il tipo dinamico corretta dell'oggetto. Per quanto riguarda la sua durata, le garanzie standard, in [except.throw],

L'oggetto eccezione viene distrutto sia dopo l'ultimo residuo gestore attivo per le uscite eccezione di mezzi diversi rethrowing, o l'ultimo oggetto di tipo std :: exception_ptr (18.8.5) che fa riferimento all'oggetto eccezione viene distrutto, se successiva

+4

Beh, non * devi *, è legale non farlo. Tuttavia, se non lo fai, stai già puntando una pistola sul tuo ginocchio. Si introduce una possibile copia intermedia e, più importante, l'affettamento degli oggetti se si cattura in base al valore. – luk32

8

Da except.throw:

un'eccezione copy-inizializza (8.5, 12.8) un oggetto temporaneo, chiamato oggetto eccezione. Il temporaneo è un lvalue e viene utilizzato per inizializzare la variabile dichiarata nel gestore di corrispondenza (15.3). Se il tipo dell'oggetto di eccezione sarebbe un tipo incompleto o un puntatore su un tipo incompleto diverso da quello (eventualmente qualificato per il cv) void il programma è mal formato.

E 'l'atto di gettare l'eccezione che copia l'oggetto eccezione nella eccezioni-zona, al di fuori di qualsiasi stack. Quindi è perfettamente lecito e consigliabile raccogliere l'eccezione per riferimento, poiché la durata dell'oggetto eccezione si estenderà fino all'ultimo possibile catch().