Si dovrebbe derivare / ereditare da std :: exception?


15

Durante la progettazione della mia prima libreria C ++ "seria", mi chiedo:

È buono lo stile da cui derivano le eccezioni std::exceptioned i suoi discendenti ?!

Anche dopo aver letto

Non ne sono ancora sicuro. Perché, oltre alla pratica comune (ma forse non buona), suppongo, come utente di una biblioteca, che una funzione di biblioteca lancerebbe std::exceptions solo quando le funzioni di biblioteca standard falliscono nell'implementazione della biblioteca e non può farci nulla. Ma ancora, quando scrivo il codice dell'applicazione, per me è molto conveniente, e anche IMHO bello guardare semplicemente a std::runtime_error. Anche i miei utenti possono fare affidamento sull'interfaccia minima definita, come what()o codici.

E ad esempio, il mio utente fornisce argomenti errati, cosa sarebbe più conveniente che lanciare un std::invalid_argument, non è vero? Quindi, combinato con l'uso ancora comune di std :: exception, vedo nel codice di altri: perché non andare oltre e derivare dalla tua classe di eccezione personalizzata (ad esempio lib_foo_exception) e anche da std::exception.

Pensieri?


Non sono sicuro di seguire. Solo perché erediti da std::exceptionnon significa che passi a std::exception. Inoltre, std::runtime_erroreredita std::exceptionin primo luogo, e il what()metodo viene std::exception, non std::runtime_error. E dovresti assolutamente creare le tue classi di eccezioni invece di generare eccezioni generiche come std::runtime_error.
Vincent Savard,

3
La differenza è che, quando la mia lib_foo_exceptionclasse deriva std::exception, l'utente della biblioteca avrebbe catturato lib_foo_exceptionsolo catturando std::exception, oltre a quando cattura solo quello libario. Quindi potrei anche chiedere Se la mia classe root delle eccezioni della libreria eredita da std :: exception .
Superlokkus,

3
@LightnessRacesinOrbit intendo "... oltre a", come "Quanti modi ci sono per catturare lib_foo_exception?" Con l'ereditarietà std::exceptionpuoi farlo da catch(std::exception)OR da catch(lib_foo_exception). Senza derivarne std::exception, lo prenderesti se e solo se , da catch(lib_foo_exception).
Superlokkus,

2
@Superlokkus: ignoriamo catch(...). È lì perché il linguaggio consente il caso che stai prendendo in considerazione (e per le librerie "comportamentali male"), ma non è la migliore pratica moderna.
Corse di leggerezza con Monica il

1
Gran parte della progettazione della gestione delle eccezioni in C ++ tende a incoraggiare catchsiti più grossolani, più generali e allo stesso modo transazioni più grossolane che modellano un'operazione di fine utente. Se lo confronti con le lingue che non promuovono l'idea di catture generalizzate std::exception&, ad esempio, spesso hanno molto più codice con try/catchblocchi intermedi interessati da errori molto specifici, il che riduce in qualche modo la generalità della gestione delle eccezioni quando inizia a posizionarsi un'enfasi molto più forte sulla gestione manuale degli errori e anche su tutti gli errori disparati che potrebbero verificarsi.

Risposte:


29

Tutte le eccezioni dovrebbero ereditare da std::exception.

Supponiamo, ad esempio, che io abbia bisogno di chiamare ComplexOperationThatCouldFailABunchOfWays()e che voglia gestire tutte le eccezioni che potrebbe generare. Se tutto eredita da std::exception, questo è facile. Ho solo bisogno di un singolo catchblocco e ho un'interfaccia standard ( what()) per ottenere i dettagli.

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
}

Se le eccezioni NON ereditano std::exception, questo diventa molto più brutto:

try {
    ComplexOperationThatCouldFailABunchOfWays();
} catch (std::exception& e) {
    cerr << e.what() << endl;
} catch (Exception& e) {
    cerr << e.Message << endl;
} catch (framework_exception& e) {
    cerr << e.Details() << endl;
}

Per quanto riguarda se lanciare runtime_erroro invalid_argumentcontro la creazione delle proprie std::exceptionsottoclassi da lanciare: la mia regola empirica è quella di introdurre una nuova sottoclasse ogni volta che devo gestire un particolare tipo di errore in modo diverso rispetto ad altri errori (cioè ogni volta che ho bisogno di un catchblocco separato ).

  • Se introduco una nuova sottoclasse di eccezioni per ogni possibile tipo di errore, anche se non ho bisogno di gestirli separatamente, ciò aggiunge molta proliferazione di classe.
  • Se riutilizzo le sottoclassi esistenti per indicare qualcosa di specifico (ovvero, se un runtime_errorlancio qui significa qualcosa di diverso da un errore di runtime generico), corro il rischio di essere in conflitto con altri usi della sottoclasse esistente.
  • Se non ho bisogno di gestire un errore in modo specifico, e se l'errore che sto generando corrisponde esattamente a uno degli errori della libreria standard esistente (come invalid_argument), quindi riutilizzo la classe esistente. In questo caso non vedo molti vantaggi nell'aggiungere una nuova classe. (Le Linee guida di base C ++ non sono d'accordo con me qui - raccomandano sempre di usare le tue classi.)

Le Linee guida di base C ++ hanno ulteriori discussioni ed esempi.


Tutte quelle e commerciali! Il C ++ è strano.
SuperJedi224,

2
@ SuperJedi224 e tutte quelle lettere diverse! L'inglese è strano.
johannes,

Questa logica non ha senso per me. Non è questocatch (...) (con i puntini di sospensione letterali)?
Maxpm

1
@Maxpm catch (...)è utile solo se non è necessario nulla con ciò che viene lanciato. Se vuoi fare qualcosa - ad esempio, mostrare o registrare il messaggio di errore specifico, come nel mio esempio - allora devi sapere di cosa si tratta.
Josh Kelley,

9

Suppongo, come utente di libreria, che una funzione di libreria lanci std :: exceptions solo quando le funzioni di libreria standard falliscono nell'implementazione della libreria e non può farci nulla

Questa è un'ipotesi errata.

I tipi di eccezione standard sono forniti per un utilizzo "più comune". Non sono progettati per essere utilizzati solo dalla libreria standard.

Sì, alla fine fai in modo che tutto erediti std::exception. Spesso, ciò comporta l'ereditarietà da std::runtime_erroro std::logic_error. Qualunque cosa sia appropriata per la classe di eccezione che stai implementando.

Questo è ovviamente soggettivo: diverse librerie popolari ignorano completamente i tipi di eccezione standard, presumibilmente per separare le librerie dalla libreria standard. Personalmente penso che sia estremamente egoista! Rende le eccezioni molto più difficili da ottenere.

Parlando personalmente, spesso lancio std::runtime_errore finisco. Ma questo sta entrando in una discussione su come rendere granulare le tue classi di eccezione, che non è quello che stai chiedendo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.