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:
// 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.
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.
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.
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
// 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:
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"; }
FinalAction
é basicamente o mesmo que o ScopeGuard
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.
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.
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
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(); });
...
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.
new
não retorna NULL, em vez disso, lança uma exceção
std::shared_ptr
e std::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;
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.
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:
#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.
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 return
dentro dos blocos try & catch.
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 a finally
palavra - chave em Java; faz uso de std::exception_ptr
e amigos, funções lambda e std::promise
, portanto, requer C++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.
#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::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á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:
break
declaração de dentro do try
e catch()
's blocos, uma vez que vivem dentro de uma função lambda;catch()
bloco após o try
: é um requisito de C ++;try
e catch()'s
, a compilação falhará porque a finally
macro será expandida para um código que desejará retornar a void
. Este poderia ser, err, um vazio ed por ter um finally_noreturn
macro das sortes.No fim das contas, não sei se algum dia eu usaria essas coisas, mas foi divertido brincar com elas. :)
catch(xxx) {}
bloco impossível no início da finally
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 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 downstream
está conectada a outro encadeamento, portanto, não é possível colocar o sentinela push(nullptr)
no destruidor downstream
porque 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);
}
}
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
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();
}
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.)
try
{
...
goto finally;
}
catch(...)
{
...
goto finally;
}
finally:
{
...
}
finally
não ocorre.