O C ++ suporta blocos 'finalmente'? (E o que é esse 'RAII' que eu continuo ouvindo?)


Respostas:


273

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:

// A class with implements RAII
class lock
{
    mutex &m_;

public:
    lock(mutex &m)
      : m_(m)
    {
        m.acquire();
    }
    ~lock()
    {
        m_.release();
    }
};

// A class which uses 'mutex' and 'lock' objects
class foo
{
    mutex mutex_; // mutex for locking 'foo' object
public:
    void bar()
    {
        lock scopeLock(mutex_); // lock object.

        foobar(); // an operation which may throw an exception

        // scopeLock will be destructed even if an exception
        // occurs, which will release the mutex and allow
        // other functions to lock the object and run.
    }
};

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.


18
"Destruição é renúncia a recursos" - DIRR ... Não, não funciona para mim. = P
Erik Forbes

14
RAII está preso - não há realmente nenhuma mudança. Tentar fazer isso seria tolice. No entanto, você deve admitir que "Aquisição de recursos é inicialização" ainda é um nome muito ruim.
Kevin

162
SBRM == Gerenciamento de recursos vinculados ao escopo
Johannes Schaub - litb

10
Qualquer pessoa com a habilidade de projetar não apenas software em geral, quanto mais técnicas aprimoradas, não pode dar uma desculpa digna para um acrônimo tão horrendo.
Hardryv

54
Isso deixa você parado quando você tem algo para limpar que não corresponde à vida útil de nenhum objeto C ++. Eu acho que você acaba com o Lifetime Equals C ++ Class Liftime ou então fica feio (LECCLEOEIGU?).
Warren P

79

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

void someFunc()
{
    DB    db("DBDesciptionString");
    // Use the db object.

} // db goes out of scope and destructor closes the connection.
  // This happens even in the presence of exceptions.

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

void someFunc()
{
    DB      db = new DB("DBDesciptionString");
    try
    {
        // Use the db object.
    }
    finally
    {
        // Can not rely on finaliser.
        // So we must explicitly close the connection.
        try
        {
            db.close();
        }
        catch(Throwable e)
        {
           /* Ignore */
           // Make sure not to throw exception if one is already propagating.
        }
    }
}

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


16
// 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.
Cemafor

10
@ Cemafor: O motivo para o C ++ não lançar exceções fora do destruidor é diferente do Java. Em Java, ele funcionará (você apenas perde a exceção original). Em C ++ é muito ruim. Mas o ponto em C ++ é que você só precisa fazer uma vez (pelo designer da classe) quando ele escreve o destruidor. Em Java, você deve fazer isso no ponto de uso. Portanto, é responsabilidade do usuário da classe escrever a mesma placa da caldeira muito tempo.
Martin York

1
Se é uma questão de ser "necessário", você também não precisa da RAII. Vamos nos livrar disso! :-) Brincadeiras à parte, o RAII é bom para muitos casos. O que o RAII faz torna mais complicado os casos em que você deseja executar algum código (não relacionado a recursos), mesmo que o código acima tenha retornado mais cedo. Para isso, você usa o gotos ou o separa em dois métodos.
Trinidad

1
@ Trinidad: Não é tão simples quanto você pensa (pois todas as suas sugestões parecem escolher as piores opções possíveis). É por isso que uma pergunta pode ser um lugar melhor para explorar isso do que os comentários.
Martin York

1
Criticar o "NÃO é necessário por causa do RAII": há muitos casos em que a adição de RAII ad-hoc seria código demais para adicionar e o try-finalmente seria extremamente apropriado.
ceztko

63

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:

namespace detail { //adapt to your "private" namespace
template <typename F>
struct FinalAction {
    FinalAction(F f) : clean_{f} {}
   ~FinalAction() { if(enabled_) clean_(); }
    void disable() { enabled_ = false; };
  private:
    F clean_;
    bool enabled_{true}; }; }

template <typename F>
detail::FinalAction<F> finally(F f) {
    return detail::FinalAction<F>(f); }

exemplo de uso:

#include <iostream>
int main() {
    int* a = new int;
    auto delete_a = finally([a] { delete a; std::cout << "leaving the block, deleting a!\n"; });
    std::cout << "doing something ...\n"; }

a saída será:

doing something...
leaving the block, deleting a!

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:

 [...]
 auto precision = std::cout.precision();
 auto set_precision_back = finally( [precision, &std::cout]() { std::cout << std::setprecision(precision); } );
 std::cout << std::setprecision(3);

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:

//strong guarantee
void copy_to_all(BIGobj const& a) {
    first_.push_back(a);
    auto undo_first_push = finally([first_&] { first_.pop_back(); });

    second_.push_back(a);
    auto undo_second_push = finally([second_&] { second_.pop_back(); });

    third_.push_back(a);
    //no necessary, put just to make easier to add containers in the future
    auto undo_third_push = finally([third_&] { third_.pop_back(); });

    undo_first_push.disable();
    undo_second_push.disable();
    undo_third_push.disable(); }

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.

#include <iostream>
int main() {
    int* a = new int;

    struct Delete_a_t {
        Delete_a_t(int* p) : p_(p) {}
       ~Delete_a_t() { delete p_; std::cout << "leaving the block, deleting a!\n"; }
        int* p_;
    } delete_a(a);

    std::cout << "doing something ...\n"; }

Pode haver um possível problema: na função 'finalmente (F f)', ele retorna um objeto FinalAction, portanto o desconstrutor pode ser chamado antes de retornar finalmente a função. Talvez devêssemos usar std :: função em vez de modelo F.
user1633272

Observe que FinalActioné basicamente o mesmo que o ScopeGuardidioma popular , apenas com um nome diferente.
anderas 5/05

1
Essa otimização é segura?
Nulano 10/08

2
@ Paolo.Bolzoni Desculpe por não responder antes, não recebi uma notificação pelo seu comentário. Eu estava preocupado que o bloco final (no qual chamo de função DLL) seria chamado antes do final do escopo (porque a variável não foi usada), mas desde então encontrei uma pergunta no SO que esclareceu minhas preocupações. Eu vincularia a ele, mas, infelizmente, não consigo mais encontrá-lo.
Nulano 24/09

1
A função disable () é uma espécie de verruga no seu design limpo. Se você deseja que o finalmente seja chamado apenas em caso de falha, por que não usar apenas a instrução catch? Não é para isso que serve?
user2445507

32

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.


Esse é um ponto muito bom. +1 para você. Porém, muitas outras pessoas não votaram em você. Espero que você não se importe de ter editado minha postagem para incluir seus comentários. (Eu dei crédito a você, é claro.) Obrigado! :)
Kevin

30

por que mesmo os idiomas gerenciados fornecem um bloqueio definitivo, apesar dos recursos serem desalocados automaticamente pelo coletor de lixo?

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...

RAII move a responsabilidade da segurança de exceção do usuário do objeto para o designer

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.


13
Exatamente ... RAII parece legal de uma perspectiva ideal. Mas eu tenho que trabalhar com APIs C convencionais o tempo todo (como funções de estilo C na API Win32 ...). É muito comum adquirir um recurso que retorna algum tipo de HANDLE, que requer alguma função como CloseHandle (HANDLE) para limpar. Usando try ... finalmente é uma boa maneira de lidar com possíveis exceções. (Felizmente, parece que shared_ptr com deleters personalizados e lambdas C ++ 11 devem fornecer algum alívio baseado em RAII que não exija a criação de classes inteiras para envolver alguma API que eu uso apenas em um local.).
James Johnston

7
@ JamesJohnston, é muito fácil escrever uma classe de invólucro que contém qualquer tipo de identificador e fornece mecânica RAII. ATL fornece um monte deles, por exemplo. Parece que você considera isso um problema demais, mas eu discordo, eles são muito pequenos e fáceis de escrever.
Mark Ransom

5
Simples sim, pequeno não. O tamanho depende da complexidade da biblioteca com a qual você está trabalhando.
precisa saber é o seguinte

1
@ MarkRansom: Existe algum mecanismo pelo qual o RAII pode fazer algo inteligente se ocorrer uma exceção durante a limpeza enquanto outra exceção estiver pendente? Em sistemas com try / finalmente, é possível - embora desajeitado - organizar as coisas para que a exceção pendente e a exceção que ocorreu durante a limpeza sejam armazenadas em uma nova CleanupFailedException. Existe alguma maneira plausível de alcançar esse resultado usando RAII?
Supercat 30/11

3
@ couling: Existem muitos casos em que um programa chama um 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.
Super12

9

Outra emulação de bloco "finalmente" usando funções lambda C ++ 11

template <typename TCode, typename TFinallyCode>
inline void with_finally(const TCode &code, const TFinallyCode &finally_code)
{
    try
    {
        code();
    }
    catch (...)
    {
        try
        {
            finally_code();
        }
        catch (...) // Maybe stupid check that finally_code mustn't throw.
        {
            std::terminate();
        }
        throw;
    }
    finally_code();
}

Vamos esperar que o compilador otimize o código acima.

Agora podemos escrever código como este:

with_finally(
    [&]()
    {
        try
        {
            // Doing some stuff that may throw an exception
        }
        catch (const exception1 &)
        {
            // Handling first class of exceptions
        }
        catch (const exception2 &)
        {
            // Handling another class of exceptions
        }
        // Some classes of exceptions can be still unhandled
    },
    [&]() // finally
    {
        // This code will be executed in all three cases:
        //   1) exception was not thrown at all
        //   2) exception was handled by one of the "catch" blocks above
        //   3) exception was not handled by any of the "catch" block above
    }
);

Se desejar, você pode agrupar esse idioma nas macros "try - finalmente":

// Please never throw exception below. It is needed to avoid a compilation error
// in the case when we use "begin_try ... finally" without any "catch" block.
class never_thrown_exception {};

#define begin_try    with_finally([&](){ try
#define finally      catch(never_thrown_exception){throw;} },[&]()
#define end_try      ) // sorry for "pascalish" style :(

Agora, o bloco "finalmente" está disponível no C ++ 11:

begin_try
{
    // A code that may throw
}
catch (const some_exception &)
{
    // Handling some exceptions
}
finally
{
    // A code that is always executed
}
end_try; // Sorry again for this ugly thing

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:

void function(std::vector<const char*> &vector)
{
    int *arr1 = (int*)malloc(800*sizeof(int));
    if (!arr1) { throw "cannot malloc arr1"; }
    ON_FINALLY({ free(arr1); });

    int *arr2 = (int*)malloc(900*sizeof(int));
    if (!arr2) { throw "cannot malloc arr2"; }
    ON_FINALLY({ free(arr2); });

    vector.push_back("good");
    ON_EXCEPTION({ vector.pop_back(); });

    ...

1
A primeira é para mim a mais legível de todas as opções apresentadas nesta página. +1
Nikos

7

Desculpe por desenterrar um thread tão antigo, mas há um erro grave no seguinte raciocínio:

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.

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

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  finally
  {
    while (!myList.empty())
    {
      delete myList.back();
      myList.pop_back();
    }
  }
}

É 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:

void DoStuff(vector<string> input)
{
  list<Foo*> myList;

  try
  {    
    for (int i = 0; i < input.size(); ++i)
    {
      Foo* tmp = new Foo(input[i]);
      if (!tmp)
        throw;

      myList.push_back(tmp);
    }

    DoSomeStuff(myList);
  }
  catch(...)
  {
  }

  while (!myList.empty())
  {
    delete myList.back();
    myList.pop_back();
  }
}

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.


17
Os idiomas gerenciados precisam finalmente ser bloqueados precisamente porque apenas um tipo de recurso é gerenciado automaticamente: memória. RAII significa que todos os recursos podem ser manipulados da mesma maneira, portanto, não há necessidade de finalmente. Se você realmente usou RAII no seu exemplo (usando ponteiros inteligentes em sua lista em vez de nus), o código seria mais simples do que o seu exemplo "finalmente". E ainda mais simples se você não verificar o valor de retorno do novo - verificar se é praticamente inútil.
Myto

7
newnão retorna NULL, em vez disso, lança uma exceção
Hasturkun

5
Você levanta uma pergunta importante, mas ela tem 2 respostas possíveis. Um deles é o dado por Myto - use ponteiros inteligentes para todas as alocações dinâmicas. O outro é usar contêineres padrão, que sempre destroem seu conteúdo após a destruição. De qualquer maneira, todo objeto alocado é de propriedade de um objeto alocado estaticamente que o libera automaticamente após a destruição. É uma pena que essas soluções melhores sejam difíceis de serem descobertas pelos programadores devido à alta visibilidade de ponteiros e matrizes simples.
Jrandom_hacker

4
O C ++ 11 aprimora isso e inclui std::shared_ptre std::unique_ptrdiretamente no stdlib.
U0b34a0f6ae 19/10/11

16
O motivo pelo qual seu exemplo é tão horrível não é porque o RAII é defeituoso, mas porque você falhou em usá-lo. Ponteiros brutos não são RAII.
precisa

6

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;

int CMyApp::Run() 
{
    __try
    {
        int i = CWinApp::Run();
        m_Exitok = MAGIC_EXIT_NO;
        return i;
    }
    __finally
    {
        if (m_Exitok != MAGIC_EXIT_NO)
            FaultHandler();
    }
}

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.


4
tenha em mente que não são realmente exceções em C ++, mas em SEH. Você pode usar os dois no código MS C ++. SEH é um manipulador de exceções de SO que é a maneira como o VB, .NET implementa exceções.
Gbjbaanb 04/10/08

e você pode usar SetUnhandledExceptionHandler para criar um manipulador de exceções não capturado 'global' - para exceções SEH.
Gbjbaanb 04/10/08

3
SEH é terrível e também impede destruidores C ++ sendo chamado
paulm

6

Conforme apontado nas outras respostas, o C ++ pode suportar finallyfuncionalidades 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). finallyNas 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:

#include <gsl/gsl_util.h>

void example()
{
    int handle = get_some_resource();
    auto handle_clean = gsl::finally([&handle] { clean_that_resource(handle); });

    // Do a lot of stuff, return early and throw exceptions.
    // clean_that_resource will always get called.
}

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 a disable()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.


3

Na verdade não, mas você pode emulá-los até certo ponto, por exemplo:

int * array = new int[10000000];
try {
  // Some code that can throw exceptions
  // ...
  throw std::exception();
  // ...
} catch (...) {
  // The finally-block (if an exception is thrown)
  delete[] array;
  // re-throw the exception.
  throw; 
}
// The finally-block (if no exception was thrown)
delete[] array;

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 returndentro dos blocos try & catch.


3
Fico feliz que você tenha mencionado o bloco finalmente pode jogar; é o que a maioria das respostas "use RAII" parece ignorar. Para evitar ter que escrever o último bloco duas vezes, você poderia fazer algo assimstd::exception_ptr e; try { /*try block*/ } catch (...) { e = std::current_exception(); } /*finally block*/ if (e) std::rethrow_exception(e);
sethobrien

1
Era tudo o que eu queria saber! Por que nenhuma das outras respostas explicou que uma captura (...) + lançamento vazio; funciona quase como um bloco finalmente? Às vezes você só precisa disso.
VinGarcia

A solução fornecida em minha resposta ( stackoverflow.com/a/38701485/566849 ) deve permitir a exceção de dentro do finallybloco.
Fabio A.

3

Eu vim com uma finallymacro que pode ser usada quase como a finallypalavra - chave em Java; faz uso de std::exception_ptre amigos, funções lambda e std::promise, portanto, requer C++11ou 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.

#include <future>

template <typename Fun>
class FinallyHelper {
    template <typename T> struct TypeWrapper {};
    using Return = typename std::result_of<Fun()>::type;

public:    
    FinallyHelper(Fun body) {
        try {
            execute(TypeWrapper<Return>(), body);
        }
        catch(...) {
            m_promise.set_exception(std::current_exception());
        }
    }

    Return get() {
        return m_promise.get_future().get();
    }

private:
    template <typename T>
    void execute(T, Fun body) {
        m_promise.set_value(body());
    }

    void execute(TypeWrapper<void>, Fun body) {
        body();
    }

    std::promise<Return> m_promise;
};

template <typename Fun>
FinallyHelper<Fun> make_finally_helper(Fun body) {
    return FinallyHelper<Fun>(body);
}

Depois, há a macro real.

#define try_with_finally for(auto __finally_helper = make_finally_helper([&] { try 
#define finally });                         \
        true;                               \
        ({return __finally_helper.get();})) \
/***/

Pode ser usado assim:

void test() {
    try_with_finally {
        raise_exception();
    }    

    catch(const my_exception1&) {
        /*...*/
    }

    catch(const my_exception2&) {
        /*...*/
    }

    finally {
        clean_it_all_up();
    }    
}

O uso de std::promisefacilita a implementação, mas provavelmente também introduz uma sobrecarga desnecessária que pode ser evitada pela reimplementação apenas das funcionalidades necessárias std::promise.


VE CAVEAT: existem algumas coisas que não funcionam como a versão java do finally. Em cima da minha cabeça:

  1. não é possível quebrar a partir de um loop externo com a breakdeclaração de dentro do trye catch()'s blocos, uma vez que vivem dentro de uma função lambda;
  2. deve haver pelo menos um catch()bloco após o try: é um requisito de C ++;
  3. se a função tiver um valor de retorno diferente de nulo, mas não houver retorno dentro dos blocos trye catch()'s, a compilação falhará porque a finallymacro será expandida para um código que desejará retornar a void. Este poderia ser, err, um vazio ed por ter um finally_noreturnmacro das sortes.

No fim das contas, não sei se algum dia eu usaria essas coisas, mas foi divertido brincar com elas. :)


Sim, foi apenas um hack rápido, mas se o programador souber o que está fazendo, pode ser útil.
Fabio A.

@ MarkLakata, atualizei a postagem com uma melhor implementação que suporta lançamentos de exceções e retornos.
Fabio A.

Parece bom. Você pode se livrar do Caveat 2 inserindo um catch(xxx) {}bloco impossível no início da finallymacro, onde xxx é um tipo falso apenas para o propósito de ter pelo menos um bloco de captura.
Mark Lakata

@ MarkLakata, pensei nisso também, mas isso tornaria impossível o uso catch(...), não é?
Fabio A.

Acho que não. Apenas crie um tipo obscuro xxxem um espaço para nome privado que nunca será usado.
Mark Lakata

2

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 sentinela nullptré 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:

    extern Queue downstream, upstream;

    int Example()
    {
        try
        {
           while(!ExitRequested())
           {
             X* x = upstream.pop();
             if (!x) break;
             x->doSomething();
             downstream.push(x);
           } 
        }
        finally { 
            downstream.push(nullptr);
        }
    }

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 downstreamestá conectada a outro encadeamento, portanto, não é possível colocar o sentinela push(nullptr)no destruidor downstreamporque não pode ser destruído neste momento ... ele precisa permanecer ativo até que o outro encadeamento receba nullptr.

Então, aqui está como usar uma classe RAII com lambda para fazer o mesmo:

    class Finally
    {
    public:

        Finally(std::function<void(void)> callback) : callback_(callback)
        {
        }
        ~Finally()
        {
            callback_();
        }
        std::function<void(void)> callback_;
    };

e aqui está como você o usa:

    extern Queue downstream, upstream;

    int Example()
    {
        Finally atEnd([](){ 
           downstream.push(nullptr);
        });
        while(!ExitRequested())
        {
           X* x = upstream.pop();
           if (!x) break;
           x->doSomething();
           downstream.push(x);
        }
    }

Olá, acredito que minha resposta acima ( stackoverflow.com/a/38701485/566849 ) satisfaz plenamente seus requisitos.
Fabio A.

1

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.

#include <vector>
#include <memory>
#include <list>
using namespace std;

class Foo
{
 ...
};

void DoStuff(vector<string> input)
{
    list<unique_ptr<Foo> > myList;

    for (int i = 0; i < input.size(); ++i)
    {
      myList.push_back(unique_ptr<Foo>(new Foo(input[i])));
    }

    DoSomeStuff(myList);
}

Mais uma introdução ao uso de unique_ptr com contêineres da Biblioteca Padrão C ++ está aqui


0

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)

try{
   // something that might throw exception
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called always,
//don't forget that it might throw some exception too
doSomeCleanUp(); 

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:

bool generalAppState = false;
try{
   // something that might throw exception

   //the very end of try block:
   generalAppState = true;
} catch( ... ){
   // what to do with uknown exception
}

//final code to be called only when exception was thrown,
//don't forget that it might throw some exception too
if( !generalAppState ){
   doSomeCleanUpOfDirtyEnd();
}

//final code to be called only when no exception is thrown
//don't forget that it might throw some exception too
else{
   cleanEnd();
}

Isso não funciona, porque o objetivo de um bloco finalmente é executar a limpeza, mesmo quando o código deve permitir que uma exceção saia do bloco de código. Considere: `tente {// coisas possivelmente jogando" B "} catch (A & a) {} finalmente {// se o C ++ o tiver ... // coisas que devem acontecer, mesmo que" B "seja lançado. } // não será executado se "B" for lançado. `IMHO, o ponto de exceção é reduzir o código de tratamento de erros, para que blocos de captura, onde quer que ocorra um lance, sejam contraproducentes. É por isso que a RAII ajuda: se aplicada liberalmente, as exceções são mais importantes nas camadas superior e inferior.
precisa saber é o seguinte

1
@burlyearly apesar de sua opinião não ser santa, eu entendi, mas em C ++ não existe, então você deve considerar isso como uma camada superior que simula esse comportamento.
Java.web 22/12/16

Downvote = Por favor, comente :)
jave.web

0

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.)


-2
try
{
  ...
  goto finally;
}
catch(...)
{
  ...
  goto finally;
}
finally:
{
  ...
}

35
Idioma bonito, mas não é exatamente o mesmo. retornar no bloco try ou catch não passará pelo seu código 'finalmente:'.
Edward KMETT

10
Vale a pena manter essa resposta errada (com classificação 0), pois Edward Kmett traz uma distinção muito importante.
precisa saber é o seguinte

12
Falha ainda maior (IMO): esse código gera todas as exceções, o que finallynão ocorre.
precisa
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.