Como aparentemente ya has supuesto, sí, C ++ proporciona las mismas capacidades sin ese mecanismo. Como tal, estrictamente hablando, el mecanismo try
/ finally
no es realmente necesario.
Dicho esto, prescindir de él impone algunos requisitos en la forma en que se diseña el resto del lenguaje. En C ++, el mismo conjunto de acciones está incorporado en el destructor de una clase. Esto funciona principalmente (¿exclusivamente?) Porque la invocación del destructor en C ++ es determinista. Esto, a su vez, conduce a algunas reglas bastante complejas sobre la vida útil de los objetos, algunas de las cuales son decididamente no intuitivas.
La mayoría de los otros idiomas proporcionan alguna forma de recolección de basura. Si bien hay cosas sobre la recolección de basura que son controvertidas (por ejemplo, su eficiencia en relación con otros métodos de administración de memoria), una cosa generalmente no lo es: el momento exacto en que el recolector de basura "limpia" un objeto no está directamente vinculado al alcance del objeto. Esto evita su uso cuando la limpieza debe ser determinista, ya sea simplemente para un funcionamiento correcto, o cuando se trata de recursos tan valiosos que la limpieza no se retrasa arbitrariamente. try
/ finally
proporciona una forma para que dichos lenguajes aborden aquellas situaciones que requieren una limpieza determinista.
Creo que aquellos que afirman que la sintaxis de C ++ para esta capacidad es "menos amigable" que la de Java, están perdiendo el punto. Peor aún, se están perdiendo un punto mucho más crucial sobre la división de responsabilidad que va mucho más allá de la sintaxis y tiene mucho más que ver con la forma en que se diseña el código.
En C ++, esta limpieza determinista ocurre en el destructor del objeto. Eso significa que el objeto puede estar (y normalmente debería estar) diseñado para limpiarse después de sí mismo. Esto va a la esencia del diseño orientado a objetos: una clase debe diseñarse para proporcionar una abstracción y hacer cumplir sus propias invariantes. En C ++, uno hace precisamente eso, y uno de los invariantes que proporciona es que cuando se destruye el objeto, los recursos controlados por ese objeto (todos ellos, no solo la memoria) se destruirán correctamente.
Java (y similares) son algo diferentes. Si bien admiten (más o menos) un tipo finalize
que teóricamente podría proporcionar capacidades similares, el soporte es tan débil que es básicamente inutilizable (y de hecho, esencialmente nunca se usa).
Como resultado, en lugar de que la clase misma pueda realizar la limpieza requerida, el cliente de la clase debe tomar medidas para hacerlo. Si hacemos una comparación suficientemente miope, a primera vista puede parecer que esta diferencia es bastante menor y Java es bastante competitivo con C ++ a este respecto. Terminamos con algo como esto. En C ++, la clase se ve así:
class Foo {
// ...
public:
void do_whatever() { if (xyz) throw something; }
~Foo() { /* handle cleanup */ }
};
... y el código del cliente se ve así:
void f() {
Foo f;
f.do_whatever();
// possibly more code that might throw here
}
En Java intercambiamos un poco más de código donde el objeto se usa por un poco menos en la clase. Inicialmente, esto parece una compensación bastante pareja. Sin embargo, en realidad está lejos de serlo, porque en el código más típico solo definimos la clase en un lugar, pero la usamos en muchos lugares. El enfoque de C ++ significa que solo escribimos ese código para manejar la limpieza en un solo lugar. El enfoque de Java significa que tenemos que escribir ese código para manejar la limpieza muchas veces, en muchos lugares, en cada lugar donde usamos un objeto de esa clase.
En resumen, el enfoque de Java básicamente garantiza que muchas abstracciones que intentamos proporcionar son "permeables": cualquier clase que requiera una limpieza determinista obliga al cliente de la clase a conocer los detalles de qué limpiar y cómo hacerlo. , en lugar de que esos detalles estén ocultos en la clase misma.
Aunque lo he llamado "el enfoque de Java" anteriormente, try
/ finally
y mecanismos similares bajo otros nombres no están completamente restringidos a Java. Para un ejemplo destacado, la mayoría (¿todos?) De los lenguajes .NET (por ejemplo, C #) proporcionan lo mismo.
Las iteraciones recientes de Java y C # también proporcionan un punto intermedio entre Java "clásico" y C ++ a este respecto. En C #, un objeto que quiere automatizar su limpieza puede implementar la IDisposable
interfaz, que proporciona un Dispose
método (al menos vagamente) similar a un destructor de C ++. Si bien esto se puede usar a través de un try
/ finally
like en Java, C # automatiza la tarea un poco más con una using
declaración que le permite definir los recursos que se crearán a medida que se ingresa un alcance y se destruyen cuando se sale del alcance. Aunque todavía está muy por debajo del nivel de automatización y certeza proporcionado por C ++, esta sigue siendo una mejora sustancial sobre Java. En particular, el diseñador de la clase puede centralizar los detalles de cómodisponer de la clase en su implementación de IDisposable
. Todo lo que queda para el programador del cliente es la menor carga de escribir una using
declaración para garantizar que la IDisposable
interfaz se utilizará cuando debería. En Java 7 y versiones posteriores, los nombres se han cambiado para proteger al culpable, pero la idea básica es básicamente idéntica.