¿Cuál es la diferencia conceptual entre finalmente y un destructor?

12

Primero, soy muy consciente de ¿Por qué no hay ¿"finalmente" construyes en C ++? pero un largo debate sobre otra pregunta parece justificar una pregunta por separado.

Aparte del problema que finally en C # y Java básicamente puede existir una sola vez (== 1) por alcance y un solo alcance puede tener múltiples (== n) destructores de C ++, creo que son esencialmente los mismos cosa. (Con algunas diferencias técnicas).

Sin embargo, otro usuario argumentó :

  

... intentaba decir que un dtor es inherentemente una herramienta para (Release sematics) y   Finalmente es inherentemente una herramienta para (cometer semántica). Si no ve por qué: considere   ¿Por qué es legítimo lanzar excepciones una encima de la otra en   Finalmente bloquea, y por eso no es lo mismo para destructores. (En algunos   sentido, es una cosa de datos frente a control. Los destructores son para liberar   Los datos, finalmente, son para liberar el control. Ellos son diferentes; sus   Desafortunadamente, C ++ los une.

¿Puede alguien aclarar esto?

    
pregunta Martin Ba 02.12.2015 - 10:28

5 respuestas

6
  • Transacción ( try )
  • Salida / respuesta de error ( catch )
  • Error externo ( throw )
  • Error del programador ( assert )
  • Revertir (lo más cercano podría ser los guardas de alcance en los idiomas que los admiten de forma nativa)
  • Liberar recursos (destructores)
  • Flujo de control independiente de la transacción Misc ( finally )

No se puede encontrar una mejor descripción para finally que el flujo de control independiente de la transacción. No necesariamente se asigna tan directamente a ningún concepto de alto nivel en el contexto de una mentalidad de recuperación de transacciones y errores, especialmente en un lenguaje teórico que tiene destructores y finally .

Lo que más me falta es una característica del lenguaje que representa directamente el concepto de revertir los efectos secundarios externos. Los guardias de alcance en idiomas como D son lo más cercano que se me ocurre que se acerca a representar ese concepto. Desde el punto de vista del flujo de control, una reversión en el alcance de una función particular debería distinguir una ruta excepcional de una normal, mientras que al mismo tiempo automatiza la reversión implícitamente de cualquier efecto secundario causado por la función si la transacción falla, pero no cuando la transacción tiene éxito. . Eso es bastante fácil de hacer con los destructores si, por ejemplo, establecemos un valor booleano a valor como succeeded a verdadero al final de nuestro bloque try para evitar la lógica de retroceso en un destructor. Pero es una forma bastante indirecta de hacer esto.

Si bien parece que no ahorraría tanto, la reversión de los efectos secundarios es una de las cosas más difíciles de corregir (por ejemplo, lo que hace que sea tan difícil escribir un contenedor genérico de excepción segura).

    
respondido por el user204677 18.12.2015 - 12:44
4

De la forma en que lo son, de la misma manera que un Ferrari y un tránsito pueden usarse para inmovilizar a las tiendas en busca de una pinta de leche aunque estén diseñados para diferentes usos.

Podría colocar una construcción try / finally en cada ámbito y limpiar todas las variables definidas en el ámbito en el bloque finally para emular un destructor de C ++. Esto es, conceptualmente, lo que hace C ++: el compilador llama automáticamente al destructor cuando una variable se sale del alcance (es decir, al final del bloque de alcance). Tendrías que organizar tu intento / finalmente, por lo que el intento es lo primero y, finalmente, lo último en cada ámbito, sin embargo. También tendría que definir un estándar para que cada objeto tenga un método con un nombre específico que use para limpiar su estado al que llamaría en el bloque finally, aunque supongo que podría dejar la administración de memoria normal que su idioma proporciona para Limpie el objeto ahora vacío cuando le guste.

Sin embargo, no sería bonito hacer esto, y aunque .NET introdujo IDispose como un destructor administrado manualmente, y el uso de bloques como un intento de hacer que la administración manual sea un poco más fácil, aún no es algo que desee hacer en la práctica.

    
respondido por el gbjbaanb 02.12.2015 - 12:13
4

Desde mi punto de vista, la principal diferencia es que un destructor en c ++ es un implícito Mecanismo (invocado automáticamente) para liberar recursos asignados mientras que el intento ... finalmente se puede utilizar como un mecanismo explícito para hacer eso.

En los programas c ++, el programador es responsable de liberar los recursos asignados. Esto generalmente se implementa en el destructor de una clase y se hace inmediatamente cuando un variable queda fuera del alcance o o cuando se llama a eliminar.

Cuando en c ++ se crea una variable local de una clase sin usar new los recursos de esas instancias se liberan implícitamente por el destructor cuando hay una excepción.

// c++
void test() {
    MyClass myClass(someParameter);
    // if there is an exception the destructor of MyClass is called automatically
    // this does not work with
    // MyClass* pMyClass = new MyClass(someParameter);

} // on test() exit the destructor of myClass is implicitly called

En java, c # y otros sistemas con gestión de memoria automática el recolector de basura del sistema decide cuándo se destruye una instancia de clase.

// c#
void test() {
    MyClass myClass = new MyClass(someParameter);
    // if there is an exception myClass is NOT destroyed so there may be memory/resource leakes

    myClass.destroy(); // this is never called
}

No hay un mecanismo implícito para eso, por lo que tienes que programarlo explícitamente con intentar finalmente

// c#
void test() {
    MyClass myClass = null;

    try {
        myClass = new MyClass(someParameter);
        ...
    } finally {
        // explicit memory management
        // even if there is an exception myClass resources are freed
        myClass.destroy();
    }

    myClass.destroy(); // this is never called
}
    
respondido por el k3b 02.12.2015 - 12:29
3

Me alegra que hayas publicado esto como una pregunta. :)

Estaba tratando de decir que los destructores y finally son conceptualmente diferentes:

  • Los destructores son para liberar recursos ( datos )
  • finally es para regresar a la persona que llama ( control )

Considere, digamos, este hipotético pseudocódigo:

try {
    bar();
} finally {
    logfile.print("bar has exited...");
}

finally aquí está resolviendo por completo un problema de control y no un problema de administración de recursos.
No tendría sentido hacer eso en un destructor por una variedad de razones:

  • Ninguna cosa está siendo "adquirida" o "creada"
  • Si no se imprime en el archivo de registro, no se producirán fugas de recursos, daños en los datos, etc.
  • Es legítimo que logfile.print falle, mientras que destrucción (conceptualmente) no puede fallar

Aquí hay otro ejemplo, esta vez como en Javascript:

var mo_document = document, mo;
function observe(mutations) {
    mo.disconnect();  // stop observing changes to prevent re-entrance
    try {
        /* modify stuff */
    } finally {
        mo.observe(mo_document);  // continue observing (conceptually, this can fail)
    }
}
mo = new MutationObserver(observe);
return observe();

En el ejemplo anterior, nuevamente, no hay recursos para liberar.
De hecho, el bloque finally está adquiriendo recursos internamente para lograr su objetivo, que podría fallar. Por lo tanto, no tiene sentido usar un destructor (si Javascript tuviera uno).

Por otro lado, en este ejemplo:

b = get_data();
try {
    a.write(b);
} finally {
    free(b);
}

finally está destruyendo un recurso, b . Es un problema de datos. El problema no se trata de devolver el control a la persona que llama, sino de evitar las fugas de recursos.
El fracaso no es una opción, y nunca debería ocurrir (conceptualmente).
Cada lanzamiento de b está necesariamente emparejado con una adquisición, y tiene sentido usar RAII.

En otras palabras, solo porque puede usar cualquiera de los dos para simular eso no significa que ambos sean el mismo problema o que ambos sean soluciones adecuadas para ambos problemas.

    
respondido por el Mehrdad 02.12.2015 - 11:16
1

La respuesta de k3b realmente lo dice muy bien:

  

un destructor en c ++ es un mecanismo implícito (invocado automáticamente)   para liberar recursos asignados mientras el intento ... finalmente puede ser   utilizado como un mecanismo explícito para hacer eso.

En cuanto a los "recursos", me gusta referirme a Jon Kalb: RAII debería significar que la Adquisición de Responsabilidad es Inicialización .

De todos modos, en cuanto a implícito vs. explícito, esto parece ser realmente:

  • Un d'tor es una herramienta para definir qué operaciones deben suceder, implícitamente, cuando finaliza la vida útil de un objeto (que a menudo coincide con el fin de alcance)
  • Un bloque-final es una herramienta para definir, explícitamente, qué operaciones se realizarán al final del alcance.
  • Además, técnicamente, siempre puedes lanzar desde finalmente, pero mira a continuación.

Creo que eso es todo para la parte conceptual, ...

... ahora hay IMHO algunos detalles interesantes:

Tampoco creo que c'tor / d'tor necesite conceptualmente "adquirir" o "crear" nada, aparte de la responsabilidad de ejecutar algún código en el destructor. Que es lo que finalmente hace también: ejecutar algún código.

Y aunque el código en un bloque finalmente puede generar una excepción, para mí no es una distinción suficiente como para decir que son conceptualmente diferentes por encima de lo explícito contra lo implícito.

(Además, no estoy convencido en absoluto de que el código "bueno" debería lanzarse finalmente, tal vez esa sea otra pregunta para sí mismo).

    
respondido por el Martin Ba 02.12.2015 - 20:36

Lea otras preguntas en las etiquetas