Como você aparentemente já supôs, sim, o C ++ fornece os mesmos recursos sem esse mecanismo. Assim, estritamente falando, o mecanismo try
/ finally
nã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
/ finally
fornece 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 finalize
que 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
/ finally
e 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 IDisposable
interface, que fornece um Dispose
método que é (pelo menos vagamente) semelhante a um destruidor de C ++. Embora isso possa ser usado via a try
/ finally
like em Java, o C # automatiza um pouco mais a tarefa com uma using
instruçã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 using
declaração para garantir que a IDisposable
interface 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.