O tratamento de exceções em C ++ é limitado a tentativa / lançamento / captura. Diferentemente do Object Pascal, Java, C # e Python, mesmo no C ++ 11, a finally
construção não foi implementada.
Tenho visto muita literatura C ++ discutindo "código de exceção seguro". Lippman escreve que o código seguro de exceção é um tópico importante, mas avançado e difícil, além do escopo de seu Primer - o que parece implicar que o código seguro não é fundamental para o C ++. Herb Sutter dedica 10 capítulos ao tópico em seu Excepcional C ++!
No entanto, parece-me que muitos dos problemas encontrados ao tentar escrever "código de exceção seguro" poderiam ser bem resolvidos se a finally
construção fosse implementada, permitindo ao programador garantir que, mesmo no caso de uma exceção, o programa possa ser restaurado para um estado seguro, estável e sem vazamentos, próximo ao ponto de alocação de recursos e código potencialmente problemático. Como um programador experiente em Delphi e C #, uso try .. finalmente bloqueia bastante o código, assim como a maioria dos programadores nessas linguagens.
Considerando todos os 'sinos e assobios' implementados no C ++ 11, fiquei surpreso ao descobrir que 'finalmente' ainda não estava lá.
Então, por que a finally
construção nunca foi implementada em C ++? Não é realmente um conceito muito difícil ou avançado de entender e ajuda bastante o programador a escrever 'código seguro de exceção'.
fonte
finally
no C ++ e quais técnicas para tratamento de exceções são usadas em seu lugar?" é válido e específico para este site. As respostas existentes cobrem isso muito bem, eu acho. Transformando-o em uma discussão sobre "Os motivos dos designers de C ++ para não incluiremfinally
vale a pena?" e "Devefinally
ser adicionado ao C ++?" e continuando a discussão nos comentários sobre a pergunta e todas as respostas não se encaixam no modelo deste site de perguntas e respostas.Respostas:
Como alguns comentários adicionais sobre a resposta de @ Nemanja (que, uma vez que cita Stroustrup, é realmente a melhor resposta possível):
É realmente apenas uma questão de entender a filosofia e os idiomas do C ++. Veja o exemplo de uma operação que abre uma conexão com o banco de dados em uma classe persistente e precisa garantir que ela seja fechada se uma exceção for lançada. Isso é uma questão de segurança de exceção e se aplica a qualquer idioma com exceções (C ++, C #, Delphi ...).
Em uma linguagem que usa
try
/finally
, o código pode se parecer com isso:Simples e direto. Existem, no entanto, algumas desvantagens:
finally
bloco, caso contrário, eu vazo recursos.DoRiskyOperation
for mais do que uma chamada de método única - se eu tiver algum processamento para fazer notry
bloco -, aClose
operação poderá acabar sendo um pouco decente daOpen
operação. Não consigo escrever minha limpeza ao lado da minha aquisição.try
/finally
blocks.A abordagem C ++ seria assim:
Isso resolve completamente todas as desvantagens da
finally
abordagem. Ele tem algumas desvantagens, mas são relativamente pequenas:ScopedDatabaseConnection
classe. No entanto, é uma implementação muito simples - apenas 4 ou 5 linhas de código.Pessoalmente, considerando essas vantagens e desvantagens, considero o RAII uma técnica muito preferível
finally
. Sua milhagem pode variar.Por fim, como o RAII é um idioma tão bem estabelecido no C ++ e para aliviar os desenvolvedores de alguns dos encargos de escrever várias
Scoped...
classes, existem bibliotecas como ScopeGuard e Boost.ScopeExit que facilitam esse tipo de limpeza determinística.fonte
using
instrução, que limpa automaticamente qualquer objeto que implementa aIDisposable
interface. Portanto, embora seja possível errar, é muito fácil acertar.try/finally
construção, porque o compilador não expõe umatry/finally
construção e a única maneira de acessá-la é através da classe baseada em classe. idioma de design, não é uma "vantagem"; é a própria definição de uma inversão de abstração.finally
. Como eu disse, sua milhagem pode variar.De Por que o C ++ não fornece uma construção "finalmente"? nas perguntas frequentes sobre estilo e técnica C ++ de Bjarne Stroustrup :
fonte
finally
construção seja sempre inútil para todo o sempre, apesar do que Strousup está dizendo. O mero fato de que escrever "código de exceção seguro" é muito importante em C ++ é a prova disso. Caramba, o C # tem dois destruidores efinally
, e ambos se acostumam.O motivo que o C ++ não possui
finally
é porque não é necessário no C ++.finally
é usado para executar algum código, independentemente de uma exceção ocorreu ou não, o que quase sempre é algum tipo de código de limpeza. No C ++, esse código de limpeza deve estar no destruidor da classe relevante e o destruidor sempre será chamado, como umfinally
bloco. O idioma de usar o destruidor para sua limpeza é chamado RAII .Dentro da comunidade C ++, pode haver mais discussões sobre o código 'exceção seguro', mas é quase igualmente importante em outros idiomas que possuem exceções. O ponto principal do código 'exceção segura' é que você pensa em que estado seu código é deixado se ocorrer uma exceção em qualquer uma das funções / métodos que você chama.
No C ++, o código 'exceção seguro' é um pouco mais importante, porque o C ++ não possui uma coleta de lixo automática que cuida de objetos que ficam órfãos devido a uma exceção.
A razão pela qual a segurança de exceção é discutida mais na comunidade C ++ provavelmente também decorre do fato de que em C ++ você precisa estar mais ciente do que pode dar errado, porque há menos redes de segurança padrão no idioma.
fonte
finally
ao padrão C ++, acho que é seguro concluir que a comunidade C ++ não considerathe absence of finally
um problema. A maioria das linguagens não possuifinally
a destruição determinística consistente que o C ++ possui. Vejo que Delphi tem os dois, mas não conheço sua história o suficiente para saber qual foi a primeira.finally
". Não me lembro de nenhuma tarefa que teria facilitado, se eu tivesse acesso a ela.Outros discutiram o RAII como a solução. É uma solução perfeitamente boa. Mas isso realmente não explica por que eles não adicionaram
finally
tão bem, já que é uma coisa amplamente desejada. A resposta para isso é mais fundamental para o design e o desenvolvimento de C ++: durante todo o desenvolvimento de C ++, os envolvidos resistiram fortemente à introdução de recursos de design que podem ser alcançados usando outros recursos sem muita confusão e, especialmente, onde isso exige a introdução de novas palavras-chave que poderiam tornar o código antigo incompatível. Como o RAII fornece uma alternativa altamente funcionalfinally
e você pode realmente criar o seu própriofinally
no C ++ 11 de qualquer maneira, houve poucas exigências.Tudo que você precisa fazer é criar uma classe
Finally
que chame a função passada para seu construtor em seu destruidor. Então você pode fazer isso:A maioria dos programadores nativos de C ++ prefere, em geral, objetos RAII projetados de maneira limpa.
fonte
Finally atEnd([&] () { database.close(); });
também, eu imagino o seguinte é melhor:{ Finally atEnd(...); try {...} catch(e) {...} }
(I levantou o finalizador para fora do try-bloco para que ele executa após os blocos catch.)Você pode usar um padrão de "interceptação" - mesmo que não queira usar o bloco try / catch.
Coloque um objeto simples no escopo necessário. No destruidor desse objeto, coloque sua lógica "final". Não importa o que, quando a pilha for desenrolada, o destruidor do objeto será chamado e você receberá seu doce.
fonte
Bem, você pode
finally
criar o seu próprio método, usando o Lambdas, que compilaria o seguinte (usando um exemplo sem RAII, é claro, não o melhor código):Veja este artigo .
fonte
Não tenho certeza se concordo com as afirmações aqui de que RAII é um superconjunto
finally
. O calcanhar de Aquiles da RAII é simples: exceções. O RAII é implementado com destruidores e sempre é errado no C ++ jogar fora de um destruidor. Isso significa que você não pode usar RAII quando precisar do seu código de limpeza. Sefinally
implementado, por outro lado, não há razão para acreditar que não seria legal jogar de umfinally
bloco.Considere um caminho de código como este:
Se tivéssemos
finally
, poderíamos escrever:Mas não há como obter o comportamento equivalente usando RAII.
Se alguém sabe como fazer isso em C ++, estou muito interessado na resposta. Eu ficaria feliz com algo que dependesse, por exemplo, de impor todas as exceções herdadas de uma única classe com alguns recursos especiais ou algo assim.
fonte
complex_cleanup
possível lançar, você pode ter um caso em que duas exceções não capturadas estão em andamento ao mesmo tempo, como faria com RAII / destruidores e o C ++ se recusa a permitir isso. Se você deseja que a exceção original seja vista,complex_cleanup
evite quaisquer exceções, exatamente como faria com os RAII / destruidores. Se você deseja quecomplex_cleanup
a exceção seja vista, acho que você pode usar blocos try / catch aninhados - embora seja uma tangente e difícil de ajustar em um comentário, vale a pena fazer uma pergunta separada.finally
bloco putativo claramente funcionaria da mesma forma que um arremesso em umcatch
bloco WRT em exceções em voo - não em chamadastd::terminate
. A questão é "por que nãofinally
em C ++?" e todas as respostas dizem "você não precisa disso ... RAII FTW!" O que quero dizer é que sim, o RAII é bom para casos simples como gerenciamento de memória, mas até que o problema das exceções seja resolvido, é necessário muito pensamento / sobrecarga / preocupação / reprojeto para ser uma solução de uso geral.