Deve-se derivar / herdar de std :: exception?

15

Ao projetar minha primeira biblioteca C ++ 'séria', estou me perguntando:

É bom estilo derivar exceções std::exceptione seus descendentes ?!

Mesmo depois de ler

Ainda não tenho certeza. Porque, além da prática comum (mas talvez não seja boa), eu assumiria, como usuário da biblioteca, que uma função da biblioteca lançaria std::exceptions somente quando as funções da biblioteca padrão falharem na implementação da biblioteca, e não poderá fazer nada a respeito. Mas ainda assim, ao escrever o código do aplicativo, para mim é muito conveniente, e também IMHO de boa aparência apenas para lançar um std::runtime_error. Além disso, meus usuários também podem confiar na interface mínima definida, como what()códigos ou.

E, por exemplo, meu usuário fornece argumentos defeituosos, o que seria mais conveniente do que lançar um std::invalid_argument, não? Então, combinado com o uso ainda comum de std :: exception que vejo nos outros códigos: Por que não ir ainda mais longe e derivar de sua classe de exceção personalizada (por exemplo, lib_foo_exception) e também de std::exception.

Pensamentos?

Superlokkus
fonte
Não tenho certeza se sigo. Só porque você herda std::exceptionnão significa que você jogou a std::exception. Além disso, std::runtime_errorherda de std::exceptionem primeiro lugar, e o what()método vem de std::exception, não std::runtime_error. E você definitivamente deve criar suas próprias classes de exceção em vez de lançar exceções genéricas como std::runtime_error.
precisa
3
A diferença é que, quando minha lib_foo_exceptionclasse deriva std::exception, o usuário da biblioteca capturaria lib_foo_exceptionapenas capturando std::exception, além de quando capturas apenas a biblioteca. Então, eu também poderia perguntar se minha classe raiz de exceção da biblioteca deve herdar de std :: exception .
Superlokkus
3
@LightnessRacesinOrbit Quero dizer "... além de", como "Quantas maneiras existem para pegar lib_foo_exception?" Com a herança de std::exceptionvocê pode fazê-lo por catch(std::exception)OU por catch(lib_foo_exception). Sem derivar std::exception, você o capturaria se e somente se , por catch(lib_foo_exception).
Superlokkus
2
@Superlokkus: Nós meio que ignoramos catch(...). Está lá porque a linguagem permite o caso que você está considerando (e para "comportar-se mal" nas bibliotecas), mas isso não é uma prática recomendada moderna.
Lightness Races com Monica
1
Muito do design do tratamento de exceções no C ++ tende a incentivar catchsites mais gerais e mais grossos, além de transações mais grossas que modelam uma operação final do usuário. Se você compará-lo a idiomas que não promovem a idéia de captura generalizada std::exception&, por exemplo, eles geralmente têm muito mais código com try/catchblocos intermediários preocupados com erros muito específicos, o que diminui um pouco a generalidade do tratamento de exceções quando ele começa a aparecer. uma ênfase muito mais forte no tratamento manual de erros e também em todos os erros díspares que poderiam ocorrer.

Respostas:

29

Todas as exceções devem herdar de std::exception.

Suponha, por exemplo, que eu precise ligar ComplexOperationThatCouldFailABunchOfWays()e que eu queira lidar com qualquer exceção que possa gerar. Se tudo é herdado std::exception, isso é fácil. Eu só preciso de um único catchbloco e tenho uma interface padrão ( what()) para obter detalhes.

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

Se as exceções NÃO herdam std::exception, isso fica muito mais feio:

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;
}

Sobre jogar runtime_errorou não invalid_argumentcriar suas próprias std::exceptionsubclasses para jogar: Minha regra geral é introduzir uma nova subclasse sempre que eu precisar lidar com um tipo específico de erro de maneira diferente de outros erros (ou seja, sempre que eu precisar de um catchbloco separado ).

  • Se eu introduzir uma nova subclasse de exceção para todo tipo concebível de erro, mesmo que não precise lidar com eles separadamente, isso adicionará muita proliferação de classes.
  • Se eu reutilizar subclasses existentes para algo específico média (ou seja, se um runtime_errorjogado aqui significa diferente algo que um erro de execução genérico), então eu corro o risco de entrar em conflito com outros usos da subclasse existente.
  • Se eu não precisar tratar especificamente de um erro e se o erro que estou lançando corresponder exatamente a um dos erros da biblioteca padrão existente (como invalid_argument), reutilizarei a classe existente. Só não vejo muitos benefícios em adicionar uma nova classe neste caso. (As Diretrizes Principais do C ++ discordam de mim aqui - elas recomendam sempre o uso de suas próprias classes.)

As diretrizes principais do C ++ têm mais discussões e exemplos.

Josh Kelley
fonte
Todos esses e comerciais! C ++ é estranho.
SuperJedi224
2
@ SuperJedi224 e todas essas letras diferentes! Inglês é estranho.
Johannes
Essa lógica não faz sentido para mim. Não é para isso catch (...)(com as reticências literais)?
Maxpm
1
O @Maxpm catch (...)só é útil se você não precisar fazer nada com o que é lançado. Se você quiser fazer algo - por exemplo, mostrar ou registrar a mensagem de erro específica, como no meu exemplo -, precisará saber o que é.
Josh Kelley
9

Eu assumiria, como usuário da biblioteca, que uma função de biblioteca lançaria std :: exceptions somente quando as funções de biblioteca padrão falhassem na implementação da biblioteca, e não poderia fazer nada a respeito.

Essa é uma suposição incorreta.

Os tipos de exceção padrão são fornecidos para uso "mais comum". Eles não foram projetados apenas para serem usados pela biblioteca padrão.

Sim, faça com que tudo acabe herdado std::exception. Muitas vezes, isso envolve a herança de std::runtime_errorou std::logic_error. O que for apropriado para a classe de exceção que você está implementando.

É claro que isso é subjetivo - várias bibliotecas populares ignoram completamente os tipos de exceção padrão, presumivelmente para separar as bibliotecas da biblioteca padrão. Pessoalmente, acho isso extremamente egoísta! Torna a captura de exceções muito mais difícil de acertar.

Falando pessoalmente, muitas vezes eu jogo std::runtime_errore acabo com isso. Mas isso está entrando em uma discussão sobre quão granular é fazer suas classes de exceção, e não é isso que você está perguntando.

Corridas de leveza com Monica
fonte