O C ++ suporta blocos ' finalmente '?
Qual é o idioma RAII ?
Qual é a diferença entre o idioma RAII do C ++ e a instrução 'using' de C # ?
O C ++ suporta blocos ' finalmente '?
Qual é o idioma RAII ?
Qual é a diferença entre o idioma RAII do C ++ e a instrução 'using' de C # ?
Respostas:
Não, o C ++ não suporta blocos 'finalmente'. O motivo é que o C ++ suporta RAII: "Aquisição de recursos é inicialização" - um nome ruim † para um conceito realmente útil.
A ideia é que o destruidor de um objeto seja responsável por liberar recursos. Quando o objeto tem duração de armazenamento automático, o destruidor do objeto será chamado quando o bloco no qual ele foi criado sair - mesmo quando esse bloco for encerrado na presença de uma exceção. Aqui está a explicação de Bjarne Stroustrup sobre o tópico.
Um uso comum para RAII é bloquear um mutex:
O RAII também simplifica o uso de objetos como membros de outras classes. Quando a classe proprietária 'é destruída, o recurso gerenciado pela classe RAII é liberado porque o destruidor da classe gerenciada por RAII é chamado como resultado. Isso significa que, quando você usa o RAII para todos os membros de uma classe que gerencia recursos, pode usar um destruidor muito simples, talvez até o padrão, para a classe proprietária, pois ela não precisa gerenciar manualmente a vida útil dos recursos dos membros. . (Obrigado a Mike B por apontar isso.)
Para aqueles familiarizados com C # ou VB.NET, você pode reconhecer que o RAII é semelhante à destruição determinística do .NET usando instruções IDisposable e 'using' . De fato, os dois métodos são muito semelhantes. A principal diferença é que o RAII liberará deterministicamente qualquer tipo de recurso - incluindo memória. Ao implementar o IDisposable no .NET (mesmo na linguagem .NET C ++ / CLI), os recursos serão liberados deterministicamente, exceto a memória. No .NET, a memória não é liberada deterministicamente; a memória é liberada apenas durante os ciclos de coleta de lixo.
† Algumas pessoas acreditam que "Destruição é renúncia a recursos" é um nome mais preciso para o idioma da RAII.
fonte
Em C ++, o finalmente NÃO é necessário devido ao RAII.
RAII move a responsabilidade da segurança de exceção do usuário do objeto para o designer (e implementador) do objeto. Eu argumentaria que este é o lugar correto, pois você só precisará obter a segurança de exceção correta uma vez (no design / implementação). Ao usar finalmente, você precisa corrigir a segurança de exceções sempre que usar um objeto.
Também IMO o código parece mais limpo (veja abaixo).
Exemplo:
Um objeto de banco de dados. Para garantir que a conexão com o banco de dados seja usada, ela deve ser aberta e fechada. Usando RAII, isso pode ser feito no construtor / destruidor.
C ++ como RAII
O uso do RAII facilita o uso de um objeto DB corretamente. O objeto DB se fechará corretamente pelo uso de um destruidor, não importa como tentemos abusar dele.
Java como finalmente
Ao usar finalmente, o uso correto do objeto é delegado ao usuário do objeto. ou seja, é responsabilidade do usuário do objeto fechar corretamente a conexão ao banco de dados. Agora você pode argumentar que isso pode ser feito no finalizador, mas os recursos podem ter disponibilidade limitada ou outras restrições e, portanto, geralmente você deseja controlar a liberação do objeto e não confiar no comportamento não determinístico do coletor de lixo.
Este é também um exemplo simples.
Quando você tem vários recursos que precisam ser liberados, o código pode ficar complicado.
Uma análise mais detalhada pode ser encontrada aqui: http://accu.org/index.php/journals/236
fonte
// Make sure not to throw exception if one is already propagating.
É importante que os destruidores de C ++ também não lançem exceções por esse motivo.RAII geralmente é melhor, mas você pode ter facilmente a semântica finalmente em C ++. Usando uma pequena quantidade de código.
Além disso, as diretrizes principais do C ++ são finalmente apresentadas.
Aqui está um link para a implementação da GSL Microsoft e um link para a implementação de Martin Moene
Bjarne Stroustrup várias vezes disse que tudo o que está na GSL significava ir no padrão eventualmente. Portanto, deve ser uma maneira à prova de futuro de usar finalmente .
Você pode se implementar facilmente, se quiser, continue lendo.
Em C ++ 11 RAII e lambdas permite fazer um general finalmente:
exemplo de uso:
a saída será:
Pessoalmente, usei isso algumas vezes para garantir o fechamento do descritor de arquivo POSIX em um programa C ++.
Ter uma classe real que gerencia recursos e evita qualquer tipo de vazamento é geralmente melhor, mas isso finalmente é útil nos casos em que fazer uma aula soa como um exagero.
Além disso, eu gosto mais do que outras línguas, finalmente, porque, se usado naturalmente, você escreve o código de fechamento próximo ao código de abertura (no meu exemplo, o new e delete ) e a destruição segue a construção na ordem LIFO, como de costume em C ++. A única desvantagem é que você obtém uma variável automática que você realmente não usa e a sintaxe lambda a torna um pouco barulhenta (no meu exemplo na quarta linha, apenas a palavra finalmente e o bloco {} à direita são significativos, o resto é essencialmente ruído).
Outro exemplo:
O membro de desativação é útil se o finalmente tiver que ser chamado apenas em caso de falha. Por exemplo, você precisa copiar um objeto em três contêineres diferentes, pode configurar o finalmente para desfazer cada cópia e desativar depois que todas as cópias tiverem êxito. Fazendo isso, se a destruição não puder ser lançada, você garante a forte garantia.
desativar exemplo:
Se você não pode usar o C ++ 11, ainda pode finalmente ter , mas o código fica um pouco mais longo. Basta definir uma estrutura apenas com um construtor e destruidor, o construtor faz referências a qualquer coisa necessária e o destruidor executa as ações necessárias. Isso é basicamente o que o lambda faz, feito manualmente.
fonte
FinalAction
é basicamente o mesmo que oScopeGuard
idioma popular , apenas com um nome diferente.Além de facilitar a limpeza com objetos baseados em pilha, o RAII também é útil porque a mesma limpeza 'automática' ocorre quando o objeto é membro de outra classe. Quando a classe proprietária é destruída, o recurso gerenciado pela classe RAII é limpo porque o dtor dessa classe é chamado como resultado.
Isso significa que, quando você atingir o RAII nirvana e todos os membros de uma classe usarem RAII (como ponteiros inteligentes), poderá obter um dtor muito simples (talvez até padrão) para a classe do proprietário, pois ele não precisa gerenciar manualmente seu vida útil dos recursos dos membros.
fonte
Na verdade, idiomas baseados em coletores de lixo precisam "finalmente" mais. Um coletor de lixo não destrói seus objetos em tempo hábil; portanto, não é possível confiar nele para limpar corretamente os problemas não relacionados à memória.
Em termos de dados alocados dinamicamente, muitos argumentariam que você deveria usar ponteiros inteligentes.
Contudo...
Infelizmente, esta é a sua própria queda. Os velhos hábitos de programação C morrem muito. Quando você estiver usando uma biblioteca escrita em estilo C ou muito C, o RAII não será usado. Antes de reescrever todo o front-end da API, é exatamente com isso que você deve trabalhar. Então a falta de "finalmente" realmente morde.
fonte
CleanupFailedException
. Existe alguma maneira plausível de alcançar esse resultado usando RAII?SomeObject.DoSomething()
método e deseja saber se (1) foi bem-sucedido, (2) falhou sem efeitos colaterais , (3) falhou com efeitos colaterais com os quais o chamador está preparado para lidar com , ou (4) falhou com efeitos colaterais que o chamador não pode lidar. Somente o chamador saberá quais situações ele pode e não pode lidar; o que o chamador precisa é uma maneira de saber qual é a situação. É uma pena que não exista um mecanismo padrão para fornecer as informações mais importantes sobre uma exceção.Outra emulação de bloco "finalmente" usando funções lambda C ++ 11
Vamos esperar que o compilador otimize o código acima.
Agora podemos escrever código como este:
Se desejar, você pode agrupar esse idioma nas macros "try - finalmente":
Agora, o bloco "finalmente" está disponível no C ++ 11:
Pessoalmente, eu não gosto da versão "macro" do idioma "finalmente" e preferiria usar a função "with_finally" pura, embora uma sintaxe seja mais volumosa nesse caso.
Você pode testar o código acima aqui: http://coliru.stacked-crooked.com/a/1d88f64cb27b3813
PS
Se você precisar finalmente de um bloco no seu código, as proteções no escopo ou as macros ON_FINALLY / ON_EXCEPTION provavelmente atenderão melhor às suas necessidades.
Aqui está um pequeno exemplo de uso ON_FINALLY / ON_EXCEPTION:
fonte
Desculpe por desenterrar um thread tão antigo, mas há um erro grave no seguinte raciocínio:
Na maioria das vezes, é necessário lidar com objetos alocados dinamicamente, números dinâmicos de objetos etc. No bloco try, algum código pode criar muitos objetos (quantos são determinados em tempo de execução) e armazenar ponteiros para eles em uma lista. Agora, este não é um cenário exótico, mas muito comum. Nesse caso, você gostaria de escrever coisas como
É claro que a própria lista será destruída ao sair do escopo, mas isso não limparia os objetos temporários que você criou.
Em vez disso, você deve seguir a rota feia:
Além disso: por que as linguagens gerenciadas fornecem finalmente um bloqueio, apesar dos recursos serem desalocados automaticamente pelo coletor de lixo?
Dica: há mais que você pode fazer com "finalmente" do que apenas desalocação de memória.
fonte
new
não retorna NULL, em vez disso, lança uma exceçãostd::shared_ptr
estd::unique_ptr
diretamente no stdlib.FWIW, o Microsoft Visual C ++ oferece suporte a try, finalmente, e historicamente foi usado em aplicativos MFC como um método de captura de exceções sérias que, de outra forma, resultariam em uma falha. Por exemplo;
Eu usei isso no passado para fazer coisas como salvar backups de arquivos abertos antes de sair. Certas configurações de depuração JIT, porém, quebram esse mecanismo.
fonte
Conforme apontado nas outras respostas, o C ++ pode suportar
finally
funcionalidades semelhantes. A implementação dessa funcionalidade provavelmente mais próxima de fazer parte da linguagem padrão é a que acompanha as Diretrizes Principais do C ++ , um conjunto de práticas recomendadas para o uso do C ++ editado por Bjarne Stoustrup e Herb Sutter. Uma implementação definally
faz parte da Biblioteca de Suporte a Diretrizes (GSL).finally
Nas Diretrizes, o uso de é recomendado ao lidar com interfaces de estilo antigo e também possui uma diretriz própria, intitulada Usar um objeto final_action para expressar a limpeza, se nenhum identificador de recurso adequado estiver disponível .Portanto, não apenas o C ++ suporta
finally
, como também é recomendável usá-lo em muitos casos de uso comuns.Um exemplo de uso da implementação GSL seria semelhante a:
A implementação e o uso do GSL são muito semelhantes aos da resposta de Paolo.Bolzoni . Uma diferença é que o objeto criado por
gsl::finally()
não possui adisable()
chamada. Se você precisar dessa funcionalidade (por exemplo, para devolver o recurso depois que ele estiver montado e nenhuma exceção estiver prevista), você poderá preferir a implementação de Paolo. Caso contrário, o uso do GSL é o mais próximo possível dos recursos padronizados.fonte
Na verdade não, mas você pode emulá-los até certo ponto, por exemplo:
Observe que o bloco final pode lançar uma exceção antes que a exceção original seja lançada novamente, descartando a exceção original. Esse é exatamente o mesmo comportamento de um bloco finalmente Java. Além disso, você não pode usar
return
dentro dos blocos try & catch.fonte
std::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
finally
bloco.Eu vim com uma
finally
macro que pode ser usada quase como afinally
palavra - chave em Java; faz uso destd::exception_ptr
e amigos, funções lambda estd::promise
, portanto, requerC++11
ou acima; ele também utiliza a expressão GCC da expressão composta , que também é suportada pelo clang.AVISO : uma versão anterior desta resposta usava uma implementação diferente do conceito com muito mais limitações.
Primeiro, vamos definir uma classe auxiliar.
Depois, há a macro real.
Pode ser usado assim:
O uso de
std::promise
facilita a implementação, mas provavelmente também introduz uma sobrecarga desnecessária que pode ser evitada pela reimplementação apenas das funcionalidades necessáriasstd::promise
.VE CAVEAT: existem algumas coisas que não funcionam como a versão java do
finally
. Em cima da minha cabeça:break
declaração de dentro dotry
ecatch()
's blocos, uma vez que vivem dentro de uma função lambda;catch()
bloco após otry
: é um requisito de C ++;try
ecatch()'s
, a compilação falhará porque afinally
macro será expandida para um código que desejará retornar avoid
. Este poderia ser, err, um vazio ed por ter umfinally_noreturn
macro das sortes.No fim das contas, não sei se algum dia eu usaria essas coisas, mas foi divertido brincar com elas. :)
fonte
catch(xxx) {}
bloco impossível no início dafinally
macro, onde xxx é um tipo falso apenas para o propósito de ter pelo menos um bloco de captura.catch(...)
, não é?xxx
em um espaço para nome privado que nunca será usado.Eu tenho um caso de uso em que acho que
finally
deveria ser uma parte perfeitamente aceitável da linguagem C ++ 11, pois acho que é mais fácil ler do ponto de vista do fluxo. Meu caso de uso é uma cadeia de encadeamentos consumidor / produtor, em que um sentinelanullptr
é enviado no final da execução para encerrar todos os encadeamentos.Se o C ++ o suportasse, você desejaria que seu código tivesse a seguinte aparência:
Eu acho que isso é mais lógico do que colocar sua declaração final no início do loop, pois ela ocorre depois que o loop foi encerrado ... mas isso é uma ilusão, porque não podemos fazê-lo em C ++. Observe que a fila
downstream
está conectada a outro encadeamento, portanto, não é possível colocar o sentinelapush(nullptr)
no destruidordownstream
porque não pode ser destruído neste momento ... ele precisa permanecer ativo até que o outro encadeamento recebanullptr
.Então, aqui está como usar uma classe RAII com lambda para fazer o mesmo:
e aqui está como você o usa:
fonte
Como muitas pessoas declararam, a solução é usar os recursos do C ++ 11 para evitar finalmente bloqueios. Um dos recursos é
unique_ptr
.Aqui está a resposta de Mephane escrita usando padrões RAII.
Mais uma introdução ao uso de unique_ptr com contêineres da Biblioteca Padrão C ++ está aqui
fonte
Eu gostaria de fornecer uma alternativa.
Se você quiser finalmente chamar o bloco sempre, basta colocá-lo após o último bloco de captura (que provavelmente deve ser
catch( ... )
a exceção não conhecida)Se você quiser finalmente bloquear como última coisa a fazer quando alguma exceção for lançada, poderá usar a variável local booleana - antes de executá-la, defina-a como false e coloque a atribuição verdadeira no final do bloco try; depois, após o bloco catch, verifique a variável valor:
fonte
Eu também acho que o RIIA não é um substituto totalmente útil para o tratamento de exceções e para finalmente ter um. BTW, eu também acho que RIIA é um nome ruim por toda parte. Eu chamo esses tipos de zeladores de classes e os uso muito. Em 95% do tempo, eles não estão inicializando nem adquirindo recursos, estão aplicando algumas alterações no escopo do escopo ou pegando algo já configurado e certificando-se de que seja destruído. Sendo este o nome padrão do site, obcecado pela internet, sou abusado por sugerir que meu nome pode ser melhor.
Eu simplesmente não acho que seja razoável exigir que toda configuração complicada de alguma lista ad hoc de coisas tenha que ter uma classe escrita para contê-la, a fim de evitar complicações ao limpar tudo de volta em face da necessidade de capturar várias tipos de exceção se algo der errado no processo. Isso levaria a muitas classes ad hoc que, de outra forma, não seriam necessárias.
Sim, é bom para as classes projetadas para gerenciar um recurso específico ou genéricas projetadas para manipular um conjunto de recursos semelhantes. Mas, mesmo que todas as coisas envolvidas tenham esses invólucros, a coordenação da limpeza pode não ser apenas uma simples invocação de destruidores por ordem inversa.
Eu acho que faz todo sentido para C ++ ter finalmente. Quero dizer, caramba, tantos pedaços de coisa foram colados nas últimas décadas que parece que pessoas estranhas de repente se tornaram conservadoras sobre algo como finalmente o que poderia ser bastante útil e provavelmente nada tão complicado quanto algumas outras coisas que foram adicionado (embora isso seja apenas um palpite da minha parte.)
fonte
fonte
finally
não ocorre.