A parte 'finalmente' de uma construção 'tentar ... pegar ... finalmente' é necessária?

25

Algumas linguagens (como C ++ e versões anteriores do PHP) não suportam a finallyparte de uma try ... catch ... finallyconstrução. É finallysempre necessário? Como o código sempre é executado, por que eu não deveria / não colocava esse código depois de um try ... catchbloco sem uma finallycláusula? Por que usar um? (Estou procurando uma razão / motivação para usar / não usar finally, não uma razão para acabar com 'catch' ou porque é legal fazê-lo.)

Agi Hammerthief
fonte
Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
maple_shaft

Respostas:

36

Além do que outros disseram, também é possível que uma exceção seja lançada dentro da cláusula catch. Considere isto:

try { 
    throw new SomeException();
} catch {
    DoSomethingWhichUnexpectedlyThrows();
}
Cleanup();

Neste exemplo, a Cleanup()função nunca é executada, porque uma exceção é lançada na cláusula catch e a próxima captura mais alta na pilha de chamadas captura isso. O uso de um bloco final remove esse risco e torna o código mais limpo para a inicialização.

Erik
fonte
4
Obrigado por uma resposta concisa e direta que não se afasta da teoria e do idioma 'X é melhor que o território Y'.
Agi Hammerthief 27/01
56

Como outros já mencionaram, não há garantia de que o código após uma tryinstrução seja executado, a menos que você capture todas as exceções possíveis. Dito isto, isto:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   handleError();
} finally {
   cleanUp();
}

pode ser reescrito 1 como:

try {
   mightThrowSpecificException();
} catch (SpecificException e) {
   try {
       handleError();
   } catch (Throwable e2) {
       cleanUp();
       throw e2;
   }
} catch (Throwable e) {
   cleanUp();
   throw e;
}
cleanUp();

Mas o último requer que você capture todas as exceções não tratadas, duplique o código de limpeza e lembre-se de lançar novamente. Portanto, finallynão é necessário , mas é útil .

O C ++ não possui finallyporque Bjarne Stroustrup acredita que o RAII é melhor ou pelo menos é suficiente para a maioria dos casos:

Por que o C ++ não fornece uma construção "finalmente"?

Como o C ++ suporta uma alternativa quase sempre melhor: A técnica "aquisição de recursos é inicialização" (TC ++ PL3, seção 14.4). A idéia básica é representar um recurso por um objeto local, para que o destruidor do objeto local libere o recurso. Dessa forma, o programador não pode esquecer de liberar o recurso.


1 O código específico para capturar todas as exceções e reproduzir novamente sem perder as informações de rastreamento de pilha varia de acordo com o idioma. Eu usei Java, onde o rastreamento de pilha é capturado quando a exceção é criada. Em C # você usaria throw;.

Doval
fonte
8
Você também precisa capturar as exceções no handleError()segundo caso, não?
Juri Robl
1
Você também pode estar lançando um erro. Gostaria de reformular isso catch (Throwable t) {}, com a tentativa .. bloco catch em torno de todo o bloco inicial (para pegar throwables de handleErrorbem)
njzk2
1
Na verdade, eu adicionaria o try-catch extra que você omitia ao chamar, o handleErro();que tornará ainda melhor o argumento sobre por que finalmente os blocos são úteis (mesmo que essa não fosse a pergunta original).
Alex
1
Essa resposta realmente não aborda a questão de por que o C ++ não possui finally, que é muito mais sutil.
precisa saber é
1
@AgiHammerthief O nested tryestá dentro do catchpara o específico excepção. Em segundo lugar, é possível que você não saiba se pode lidar com o erro com êxito até examinar a exceção ou se a causa da exceção também impede que você lide com o erro (pelo menos nesse nível). Isso é bastante comum ao fazer E / S. O relançamento existe porque a única maneira de garantir cleanUpexecuções é capturar tudo , mas o código original permitiria que as exceções originárias do catch (SpecificException e)bloco se propagassem para cima.
Doval
22

finally Os blocos geralmente são usados ​​para limpar recursos que podem ajudar na legibilidade ao usar várias instruções de retorno:

int DoSomething() {
    try {
        open_connection();
        return get_result();
    }
    catch {
        return 2;
    }
    finally {
        close_connection();
    }
}

vs

int DoSomething() {
    int result;
    try {
        open_connection();
        result = get_result();
    }
    catch {
        result = 2;
    }
    close_connection();
    return result;
}
AlexFoxGill
fonte
2
Eu acho que essa é a melhor resposta. Usar finalmente um substituto para uma exceção genérica parece uma merda. O caso de uso correto é limpar recursos ou operações análogas.
Kik
3
Talvez ainda mais comum esteja retornando dentro do bloco try, em vez de dentro do bloco catch.
Michael Anderson
Na minha opinião, o código não explica adequadamente o uso de finally. (Eu usar o código como no segundo bloco como várias instruções de retorno são desencorajados onde eu trabalho.)
Agi Hammerthief
15

Como você aparentemente já supôs, sim, o C ++ fornece os mesmos recursos sem esse mecanismo. Assim, estritamente falando, o mecanismo try/ finallynão é realmente necessário.

Dito isto, ficar sem ele impõe alguns requisitos na maneira como o restante do idioma é projetado. Em C ++, o mesmo conjunto de ações é incorporado no destruidor de uma classe. Isso funciona principalmente (exclusivamente?) Porque a chamada de destruidor em C ++ é determinística. Isso, por sua vez, leva a algumas regras bastante complexas sobre a vida útil dos objetos, algumas das quais são decididamente não intuitivas.

A maioria dos outros idiomas fornece alguma forma de coleta de lixo. Embora existam coisas controversas sobre a coleta de lixo (por exemplo, sua eficiência em relação a outros métodos de gerenciamento de memória), uma coisa geralmente não é: a hora exata em que um objeto será "limpo" pelo coletor de lixo não está diretamente ligada. para o escopo do objeto. Isso evita seu uso quando a limpeza precisa ser determinística, quando é simplesmente necessária para a operação correta, ou quando se lida com recursos tão preciosos que sua limpeza não seja mais retardada arbitrariamente. try/ finallyfornece uma maneira para esses idiomas lidarem com as situações que exigem essa limpeza determinística.

Eu acho que aqueles que afirmam que a sintaxe C ++ para esse recurso é "menos amigável" do que o Java estão perdendo o ponto. Pior, eles estão perdendo um ponto muito mais crucial sobre a divisão de responsabilidades que vai muito além da sintaxe e tem muito mais a ver com a maneira como o código é projetado.

No C ++, essa limpeza determinística acontece no destruidor do objeto. Isso significa que o objeto pode ser (e normalmente deveria ser) projetado para limpar a si próprio. Isso vai para a essência do design orientado a objetos - uma classe deve ser projetada para fornecer uma abstração e impor seus próprios invariantes. No C ++, é preciso exatamente isso - e um dos invariantes que ele fornece é que, quando o objeto é destruído, os recursos controlados por esse objeto (todos eles, não apenas a memória) serão destruídos corretamente.

Java (e similares) são um pouco diferentes. Enquanto eles suportam (meio que) um finalizeque teoricamente poderia fornecer recursos semelhantes, o suporte é tão fraco que é basicamente inutilizável (e, de fato, essencialmente nunca usado).

Como resultado, em vez de a própria classe poder fazer a limpeza necessária, o cliente da classe precisa tomar medidas para fazê-lo. Se fizermos uma comparação suficientemente míope, pode parecer à primeira vista que essa diferença é bastante pequena e o Java é bastante competitivo com o C ++ nesse aspecto. Terminamos com algo assim. No C ++, a classe se parece com isso:

class Foo {
    // ...
public:
    void do_whatever() { if (xyz) throw something; }
    ~Foo() { /* handle cleanup */ }
};

... e o código do cliente se parece com isso:

void f() { 
    Foo f;
    f.do_whatever();
    // possibly more code that might throw here
}

Em Java, trocamos um pouco mais de código em que o objeto é usado por um pouco menos na classe. Este inicialmente parece com um bonito mesmo trade-off. Na realidade, está longe disso, porque na maioria dos códigos típicos definimos apenas a classe em um lugar, mas a usamos em muitos lugares. A abordagem C ++ significa que apenas escrevemos esse código para lidar com a limpeza em um só lugar. A abordagem Java significa que precisamos escrever esse código para lidar com a limpeza várias vezes, em muitos lugares - em todos os lugares em que usamos um objeto dessa classe.

Resumindo, a abordagem Java basicamente garante que muitas abstrações que tentamos fornecer são "vazadas" - toda e qualquer classe que requer limpeza determinística obriga o cliente da classe a saber sobre os detalhes do que limpar e como fazer a limpeza , em vez de esses detalhes serem ocultos na própria classe.

Embora eu chamei de "a abordagem Java" acima, try/ finallye mecanismos semelhantes sob outros nomes não são totalmente restritos ao Java. Para um exemplo de destaque, a maioria (todas?) Das linguagens .NET (por exemplo, C #) fornece o mesmo.

As iterações recentes de Java e C # também fornecem algo a meio caminho entre Java "clássico" e C ++ a esse respeito. Em C #, um objeto que deseja automatizar sua limpeza pode implementar a IDisposableinterface, que fornece um Disposemétodo que é (pelo menos vagamente) semelhante a um destruidor de C ++. Embora isso possa ser usado via a try/ finallylike em Java, o C # automatiza um pouco mais a tarefa com uma usinginstrução que permite definir recursos que serão criados quando um escopo for inserido e destruído quando o escopo for encerrado. Embora ainda esteja muito aquém do nível de automação e certeza fornecido pelo C ++, isso ainda é uma melhoria substancial em relação ao Java. Em particular, o designer de classe pode centralizar os detalhes de comodispor da classe na sua implementação de IDisposable. Tudo o que resta para o programador do cliente é o menor ônus de escrever uma usingdeclaração para garantir que a IDisposableinterface seja usada quando deveria. No Java 7 e mais recente, os nomes foram alterados para proteger os culpados, mas a idéia básica é basicamente idêntica.

Jerry Coffin
fonte
1
Resposta perfeita. Destruidores são O recurso deve ter em C ++.
Thomas Eding
13

Não posso acreditar que mais ninguém levantou isso (sem trocadilhos) - você não precisa de uma cláusula de captura !

Isso é perfeitamente razoável:

try 
{
   AcquireManyResources(); 
   DoSomethingThatMightFail(); 
}
finally 
{
   CleanUpThoseResources(); 
}

Nenhuma cláusula catch em qualquer lugar é vista, porque esse método não pode fazer nada útil com essas exceções; eles são deixados para propagar o backup da pilha de chamadas para um manipulador que puder . Capturar e repetir exceções em todos os métodos é uma Má Idéia, especialmente se você está apenas repetindo a mesma exceção. Isso contraria completamente a forma como o Manuseio de Exceções Estruturadas deve funcionar (e está muito perto de retornar um "código de erro" de cada método, apenas na "forma" de uma Exceção).

O que este método não tem que fazer, porém, é para limpar depois de si, de modo que o "mundo exterior" não precisa saber nada sobre a confusão que ele ficou-se em. A cláusula Finalmente faz exatamente isso - não importa como os métodos chamados se comportem, a cláusula Finalmente será executada "na saída" do método (e o mesmo se aplica a todas as cláusulas Finalmente entre o ponto em que a Exceção é lançada e a eventual cláusula catch que lida com isso); cada um é executado como a pilha de chamadas "desenrola".

Phill W.
fonte
9

O que aconteceria se fosse lançada uma exceção que você não esperava. A tentativa sairia no meio dela e nenhuma cláusula catch será executada.

O bloco finalmente é ajudar com isso e garantir que, independentemente da exceção, a limpeza ocorra.

catraca arrepiante
fonte
4
Essa não é uma razão suficiente para a finally, pois você pode evitar exceções "inesperadas" com catch(Object)ou catch(...)catch-alls.
precisa saber é o seguinte
1
Isso soa como uma solução alternativa. Conceitualmente, finalmente, é mais limpo. Embora eu deva confessar que raramente o uso.
precisa saber é o seguinte
7

Algumas linguagens oferecem construtores e destruidores para seus objetos (por exemplo, C ++, acredito). Com esses idiomas, você pode fazer a maioria (sem dúvida todos) do que geralmente é feito finallyem um destruidor. Como tal - nessas línguas - uma finallycláusula pode ser supérflua.

Em uma linguagem sem destruidores (por exemplo, Java), é difícil (talvez até impossível) obter a limpeza correta sem a finallycláusula. NB - Em Java, existe um finalisemétodo, mas não há garantia de que ele será chamado.

OldCurmudgeon
fonte
Pode ser útil observar que os destruidores ajudam na limpeza dos recursos quando a destruição é determinística . Se não sabemos quando o objeto será destruído e / ou coletado de lixo, os destruidores não serão suficientemente seguros.
Morwenn 26/01
@ Morwenn - Bom ponto. Eu sugeri isso com minha referência ao Java, finalisemas eu preferiria não entrar nos argumentos políticos sobre destruidores / finalistas neste momento.
OldCurmudgeon
Em C ++, a destruição é determinística. Quando o escopo que contém um objeto automático sai (por exemplo, é retirado da pilha), seu destruidor é chamado. (C ++ permite alocar objetos na pilha, não apenas a pilha.)
Rob K
@RobK - E essa é a funcionalidade exata de um finalisemas com um sabor extensível e um mecanismo semelhante ao oop - muito expressivo e comparável ao finalisemecanismo de outras linguagens.
OldCurmudgeon
1

Tente finalmente e tente pegar são duas coisas diferentes que compartilham apenas a palavra-chave: "tente". Pessoalmente, eu gostaria de ver isso diferente. A razão pela qual você os vê juntos é porque as exceções produzem um "salto".

E o try finalmente foi projetado para executar código, mesmo que o fluxo de programação salte. Seja por uma exceção ou por qualquer outro motivo. É uma maneira elegante de adquirir um recurso e garantir que ele seja limpo depois sem ter que se preocupar com saltos.

Pieter B
fonte
3
No .NET, eles são implementados usando mecanismos separados; em Java, no entanto, a única construção reconhecida pela JVM é semanticamente equivalente a "on error goto", um padrão que suporta diretamente, try catchmas não try finally; código usando o último é convertido em código usando apenas o primeiro, copiando o conteúdo do finallybloco em todos os pontos do código em que ele pode precisar ser executado.
Supercat
@ supercat nice, obrigado pelas informações extras sobre Java.
Pieter B
1

Como essa pergunta não especifica C ++ como uma linguagem, considerarei uma combinação de C ++ e Java, pois eles adotam uma abordagem diferente para a destruição de objetos, o que está sendo sugerido como uma das alternativas.

Motivos pelos quais você pode usar um bloco finalmente, em vez de codificar após o bloco try-catch

  • você retorna cedo do bloco try: considere isso

    Database db = null;
    try {
     db = open_database();
     if(db.isSomething()) {
       return 7;
     }
     return db.someThingElse();
    } finally {
      if(db!=null)
        db.close();
    }
    

    comparado com:

    Database db = null;
    int returnValue = 0;
    try {
     db = open_database();
     if(db.isSomething()) {
       returnValue = 7;
     } else {
       returnValue = db.someThingElse();
     }
    } catch(Exception e) {
      if(db!=null)
        db.close();
    }
    return returnValue;
    
  • você retorna cedo do (s) bloco (s) de captura: Compare

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      return 7;
    } catch (DBIsADonkeyException e ) {
      return 11;
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null) 
        db.close();
      return 7;
    } catch (DBIsADonkeyException e ) {
      if(db!=null)
        db.close();
      return 11;
    }           
    db.close();
    
  • Você repete exceções. Comparar:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      throw convertToRuntimeException(e,"DB was wonkey");
    } finally {
      if(db!=null)
        db.close();
    }
    

    vs:

    Database db = null;
    try {
     db = open_database();
     db.doSomething();
    } catch (DBIntegrityException e ) {
      if(db!=null)
        db.close();
      throw convertToRuntimeException(e,"DB was wonkey");
    } 
    if(db!=null)
      db.close();
    

Esses exemplos não fazem parecer muito ruins, mas muitas vezes você tem vários desses casos interagindo e mais de um tipo de exceção / recurso em execução. finallypode ajudar a impedir que seu código se torne um pesadelo de manutenção emaranhado.

Agora, em C ++, eles podem ser manipulados com objetos baseados em escopo. Mas, na IMO, há duas desvantagens nessa abordagem: 1. A sintaxe é menos amigável. 2. A ordem de construção, sendo o inverso da ordem de destruição, pode tornar as coisas menos claras.

Em Java, você não pode conectar o método finalize para fazer sua limpeza, pois não sabe quando isso acontecerá - (bem, você pode, mas esse é um caminho cheio de condições divertidas de corrida - a JVM tem muito escopo para decidir quando destruir coisas - muitas vezes não é quando você espera - mais cedo ou mais tarde do que você poderia esperar - e isso pode mudar à medida que o compilador de hot spot entra em ação ... suspiro ...)

Michael Anderson
fonte
1

Tudo o que é lógico "necessário" em uma linguagem de programação são as instruções:

assignment a = b
subtract a from b
goto label
test a = 0
if true goto label

Qualquer algoritmo pode ser implementado usando apenas as instruções acima; todas as outras construções de linguagem existem para facilitar a gravação e a compreensão dos programas por outros programadores.

Consulte o computador antigo para obter hardware real usando um conjunto de instruções tão mínimo.

James Anderson
fonte
1
Sua resposta é certamente verdadeira, mas não codifico na montagem; é muito doloroso. Estou perguntando por que usar um recurso que não vejo sentido nos idiomas que o suportam, não qual é o conjunto mínimo de instruções de um idioma.
Agi Hammerthief
1
O ponto é que qualquer linguagem que implementa apenas essas 5 operações pode implementar qualquer algoritmo - embora de forma tortuosa. A maioria dos operadores / vers em linguagens de alto nível não é "necessária" se o objetivo é apenas implementar um algoritmo. Se o objetivo é ter um rápido desenvolvimento de código legível e legível, então a maioria é necessária, mas "legível" e "legível" não são mensuráveis ​​e extremamente subjetivas. Os desenvolvedores de linguagem legal possuem muitos recursos: se você não tiver um uso para alguns deles, não os use.
James Anderson
0

Na verdade, a maior diferença para mim geralmente está nos idiomas que suportam, finallymas não possuem destruidores, porque você pode modelar toda a lógica associada à "limpeza" (que separarei em duas categorias) através de destruidores em um nível central, sem lidar manualmente com a limpeza lógica em todas as funções relevantes. Quando vejo código C # ou Java fazendo coisas como desbloquear manualmente mutexes e fechar arquivos em finallyblocos, isso parece desatualizado e mais ou menos como código C quando tudo isso é automatizado em C ++ por meio de destruidores de maneira a libertar os humanos dessa responsabilidade.

No entanto, eu ainda acharia uma conveniência leve se o C ++ incluísse finallye é porque existem dois tipos de limpeza:

  1. Destruindo / liberando / desbloqueando / fechando / etc recursos locais (destruidores são perfeitos para isso).
  2. Desfazendo / revertendo efeitos colaterais externos (os destruidores são adequados para isso).

O segundo, pelo menos, não é tão intuitivo para a idéia de destruição de recursos, embora você possa fazer isso muito bem com os guardas de escopo que revertem automaticamente as alterações quando são destruídas antes de serem confirmadas. Há finally, sem dúvida, fornece pelo menos um pouco (apenas um pouquinho) mecanismo mais simples para o trabalho de guardas de escopo.

No entanto, um mecanismo ainda mais direto seria um rollbackbloco que eu nunca vi em nenhum idioma antes. É meio que um sonho meu, se eu alguma vez desenhei uma linguagem que envolvia o tratamento de exceções. Seria parecido com isto:

try
{
    // Cause external side effects. These side effects should
    // be undone if we don't finish successfully.
}
rollback
{
    // Reverse external side effects. This block is *only* executed 
    // if the 'try' block above faced a premature return out 
    // of the function. It is different from 'finally' which 
    // gets executed regardless of whether or not the function 
    // exited prematurely. This block *only* gets executed if we 
    // exited prematurely from  the try block so that we can undo 
    // whatever side effects it failed to finish making. If the try 
    // block succeeded and didn't face a premature exit, then we 
    // don't want this block to execute.
}

Essa seria a maneira mais direta de modelar reversões de efeitos colaterais, enquanto os destruidores são praticamente o mecanismo perfeito para a limpeza de recursos locais. Agora, ele salva apenas algumas linhas de código extras da solução de proteção de escopo, mas a razão pela qual eu quero ver uma linguagem com isso é que a reversão do efeito colateral tende a ser o aspecto mais negligenciado (mas mais complicado) do tratamento de exceções em idiomas que giram em torno da mutabilidade. Acho que esse recurso incentivaria os desenvolvedores a pensarem sobre como lidar com exceções da maneira correta em termos de reversão de transações sempre que as funções causarem efeitos colaterais e não forem concluídas e, como um bônus colateral, quando as pessoas perceberem o quão difícil pode ser a reversão adequada, eles podem preferir escrever mais funções sem efeitos colaterais em primeiro lugar.

Há também alguns casos obscuros em que você só deseja fazer coisas diversas, independentemente de sair de uma função, independentemente de como ela saiu, como talvez registrar um registro de data e hora. Não finallyé sem dúvida a solução mais simples e perfeita para o trabalho, uma vez que tenta instanciar um objeto apenas para usar seu destrutor para o único propósito de registrar um timestamp só se sente realmente estranho (mesmo que você pode fazê-lo muito bem e muito convenientemente com lambdas )


fonte
-9

Como muitas outras coisas incomuns sobre a linguagem C ++, a falta de um try/finallyconstruto é uma falha de design, se é que você pode chamá-lo assim em uma linguagem que freqüentemente parece não ter feito nenhum trabalho de design real .

RAII (o uso da chamada de destruidor determinístico baseado em escopo em objetos baseados em pilha para limpeza) tem duas falhas sérias. A primeira é que requer o uso de objetos baseados em pilha , que são uma abominação que viola o Princípio de Substituição de Liskov. Há muitas boas razões pelas quais nenhuma outra linguagem OO antes ou desde que o C ++ as usou - no epsilon; D não conta, pois se baseia muito em C ++ e não tem participação de mercado de qualquer maneira - e explicar os problemas que eles causam está além do escopo desta resposta.

Segundo, o que se finallypode fazer é um superconjunto de destruição de objetos. Muito do que é feito com RAII em C ++ seria descrito na linguagem Delphi, que não possui coleta de lixo, com o seguinte padrão:

myObject := MyClass.Create(arguments);
try
   doSomething(myObject);
finally
   myObject.Free();
end;

Este é o padrão RAII explicitado; se você criar uma rotina C ++ que contenha apenas o equivalente à primeira e terceira linhas acima, o que o compilador geraria acabaria parecido com o que escrevi em sua estrutura básica. E como é o único acesso à try/finallyconstrução que o C ++ fornece, os desenvolvedores de C ++ acabam com uma visão bastante míope try/finally: quando tudo que você tem é um martelo, tudo começa a parecer um destruidor, por assim dizer.

Mas há outras coisas que um desenvolvedor experiente pode fazer com uma finallyconstrução. Não se trata de destruição determinística, mesmo diante de uma exceção que está sendo levantada; trata-se de execução de código determinístico , mesmo em caso de exceção.

Aqui está outra coisa que você normalmente vê no código Delphi: Um objeto de conjunto de dados com controles de usuário vinculados a ele. O conjunto de dados contém dados de uma fonte externa e os controles refletem o estado dos dados. Se você estiver prestes a carregar um monte de dados no seu conjunto de dados, desabilitará temporariamente a ligação de dados para que não faça coisas estranhas à sua interface do usuário, tentando atualizá-lo várias vezes a cada novo registro inserido. , então você codificaria assim:

dataset.DisableControls();
try
   LoadData(dataset);
finally
   dataset.EnableControls();
end;

Claramente, não há nenhum objeto sendo destruído aqui, nem a necessidade de um. O código é simples, conciso, explícito e eficiente.

Como isso seria feito em C ++? Bem, primeiro você teria que codificar uma classe inteira . Provavelmente seria chamado DatasetEnablerou algo assim. Toda a sua existência seria como um ajudante da RAII. Então você precisaria fazer algo assim:

dataset.DisableControls();
{
   raiiGuard = DatasetEnabler(dataset);
   LoadData(dataset);
}

Sim, esses chavetas aparentemente supérfluas são necessárias para gerenciar o escopo adequado e garantir que o conjunto de dados seja reativado imediatamente e não no final do método. Portanto, o que você acaba usando não exige menos linhas de código (a menos que você use chaves egípcias). Requer a criação de um objeto supérfluo, com sobrecarga. (O código C ++ não deve ser rápido?) Não é explícito, mas depende da mágica do compilador. O código que é executado não é descrito em nenhum lugar neste método, mas reside em uma classe totalmente diferente, possivelmente em um arquivo totalmente diferente . Em suma, não é de forma alguma uma solução melhor do que ser capaz de escrever o try/finallybloco você mesmo.

Esse tipo de problema é bastante comum no design de linguagem e existe um nome para ele: inversão de abstração. Ocorre quando uma construção de alto nível é construída sobre uma construção de baixo nível e, em seguida, a construção de baixo nível não é diretamente suportada no idioma, exigindo que aqueles que desejam usá-lo o reimplemente em termos de construção de alto nível, muitas vezes sujeita a penalidades acentuadas à legibilidade e à eficiência do código.

Mason Wheeler
fonte
Os comentários devem esclarecer ou melhorar uma pergunta e resposta. Se você gostaria de ter uma discussão sobre esta resposta, acesse a sala de bate-papo. Obrigado.
maple_shaft