Para algumas linguagens (por exemplo, C ++), o vazamento de recursos não deve ser uma razão
C ++ é baseado em RAII.
Se você tem um código que pode falhar, retornar ou lançar (ou seja, a maioria dos códigos normais), então você deve ter seu ponteiro dentro de um ponteiro inteligente (supondo que você tenha um bom motivo para não ter seu objeto criado na pilha).
Os códigos de retorno são mais detalhados
Eles são prolixos e tendem a se desenvolver em algo como:
if(doSomething())
{
if(doSomethingElse())
{
if(doSomethingElseAgain())
{
// etc.
}
else
{
// react to failure of doSomethingElseAgain
}
}
else
{
// react to failure of doSomethingElse
}
}
else
{
// react to failure of doSomething
}
No final, seu código é uma coleção de instruções identificadas (eu vi esse tipo de código no código de produção).
Este código pode muito bem ser traduzido em:
try
{
doSomething() ;
doSomethingElse() ;
doSomethingElseAgain() ;
}
catch(const SomethingException & e)
{
// react to failure of doSomething
}
catch(const SomethingElseException & e)
{
// react to failure of doSomethingElse
}
catch(const SomethingElseAgainException & e)
{
// react to failure of doSomethingElseAgain
}
Que separa claramente o código do processamento de erros, o que pode ser uma coisa boa .
Códigos de retorno são mais frágeis
Se não for algum aviso obscuro de um compilador (veja o comentário de "phjr"), eles podem ser facilmente ignorados.
Com os exemplos acima, suponha que alguém se esquece de lidar com seu possível erro (isso acontece ...). O erro é ignorado quando "retornado" e possivelmente explodirá mais tarde (ou seja, um ponteiro NULL). O mesmo problema não acontecerá com exceção.
O erro não será ignorado. Às vezes, você quer que ele não exploda ... Portanto, você deve escolher com cuidado.
Códigos de retorno às vezes devem ser traduzidos
Digamos que temos as seguintes funções:
- doSomething, que pode retornar um int chamado NOT_FOUND_ERROR
- doSomethingElse, que pode retornar um bool "falso" (para falha)
- doSomethingElseAgain, que pode retornar um objeto Error (com as variáveis __LINE__, __FILE__ e metade da pilha.
- do TryToDoSomethingWithAllThisMess que, bem ... Use as funções acima e retorne um código de erro do tipo ...
Qual é o tipo de retorno de doExperimenteToDoSomethingWithAllThisMess se uma de suas funções chamadas falhar?
Códigos de retorno não são uma solução universal
Os operadores não podem retornar um código de erro. Os construtores C ++ também não.
Códigos de retorno significam que você não pode encadear expressões
O corolário do ponto acima. E se eu quiser escrever:
CMyType o = add(a, multiply(b, c)) ;
Não posso, porque o valor de retorno já está em uso (e às vezes não pode ser alterado). Assim, o valor de retorno passa a ser o primeiro parâmetro, enviado como referência ... Ou não.
Exceções são digitadas
Você pode enviar classes diferentes para cada tipo de exceção. As exceções de recursos (ou seja, sem memória) devem ser leves, mas qualquer outra coisa pode ser tão pesada quanto necessário (eu gosto da exceção Java que me dá toda a pilha).
Cada captura pode então ser especializada.
Nunca use pegar (...) sem jogar de novo
Normalmente, você não deve ocultar um erro. Se você não relançar, no mínimo, registre o erro em um arquivo, abra uma caixa de mensagem, o que for ...
As exceções são ... NUKE
O problema com a exceção é que o uso excessivo deles irá produzir um código cheio de try / catch. Mas o problema está em outro lugar: quem tenta / captura seu código usando o contêiner STL? Ainda assim, esses contêineres podem enviar uma exceção.
Claro, em C ++, nunca deixe uma exceção sair de um destruidor.
As exceções são ... síncronas
Certifique-se de capturá-los antes que eles coloquem seu tópico de joelhos ou se propaguem dentro do loop de mensagens do Windows.
A solução poderia ser misturá-los?
Então eu acho que a solução é jogar quando algo não deveria acontecer. E quando algo puder acontecer, use um código de retorno ou um parâmetro para permitir que o usuário reaja a isso.
Portanto, a única questão é "o que é algo que não deveria acontecer?"
Depende do contrato de sua função. Se a função aceita um ponteiro, mas especifica que o ponteiro não deve ser NULL, então está ok para lançar uma exceção quando o usuário envia um ponteiro NULL (a questão é, em C ++, quando o autor da função não usou referências em vez disso de ponteiros, mas ...)
Outra solução seria mostrar o erro
Às vezes, seu problema é que você não quer erros. Usar exceções ou códigos de retorno de erro é legal, mas ... Você quer saber sobre isso.
No meu trabalho, usamos uma espécie de "Assert". Vai, dependendo dos valores de um arquivo de configuração, não importa as opções de depuração / liberação de compilação:
- registre o erro
- abra uma caixa de mensagem com um "Ei, você tem um problema"
- abra uma caixa de mensagem com um "Ei, você tem um problema, deseja depurar"
Tanto no desenvolvimento quanto no teste, isso permite que o usuário identifique o problema exatamente quando ele é detectado, e não depois (quando algum código se preocupa com o valor de retorno ou dentro de uma captura).
É fácil adicionar ao código legado. Por exemplo:
void doSomething(CMyObject * p, int iRandomData)
{
// etc.
}
leva um tipo de código semelhante a:
void doSomething(CMyObject * p, int iRandomData)
{
if(iRandomData < 32)
{
MY_RAISE_ERROR("Hey, iRandomData " << iRandomData << " is lesser than 32. Aborting processing") ;
return ;
}
if(p == NULL)
{
MY_RAISE_ERROR("Hey, p is NULL !\niRandomData is equal to " << iRandomData << ". Will throw.") ;
throw std::some_exception() ;
}
if(! p.is Ok())
{
MY_RAISE_ERROR("Hey, p is NOT Ok!\np is equal to " << p->toString() << ". Will try to continue anyway") ;
}
// etc.
}
(Eu tenho macros semelhantes que estão ativas apenas na depuração).
Note que na produção o arquivo de configuração não existe, então o cliente nunca vê o resultado desta macro ... Mas é fácil ativá-la quando necessário.
Conclusão
Ao codificar usando códigos de retorno, você está se preparando para o fracasso e espera que sua fortaleza de testes seja segura o suficiente.
Ao codificar usando exceção, você sabe que seu código pode falhar e geralmente coloca o contra-fogo na posição estratégica escolhida em seu código. Mas geralmente, seu código é mais sobre "o que deve fazer" do que "o que temo que aconteça".
Mas quando você codifica, você deve usar a melhor ferramenta à sua disposição e, às vezes, é "Nunca esconda um erro e mostre-o o mais rápido possível". A macro que falei acima segue essa filosofia.