Pregunta

Posible duplicado:
¿Cuándo es útil un bloque de intento de función?
Diferencia entre la sintaxis de prueba de prueba para la función

Este código arroja un int excepción al construir el Dog Objeto dentro de la clase UseResources. los int la excepción es atrapada por una normalidad try-catch Bloque y las salidas del código:

Cat()  
Dog()  
~Cat()  
Inside handler

#include <iostream>
using namespace std;

class Cat
{
    public:
    Cat() { cout << "Cat()" << endl; }
    ~Cat() { cout << "~Cat()" << endl; }
};

class Dog
{
    public:
    Dog() { cout << "Dog()" << endl; throw 1; }
    ~Dog() { cout << "~Dog()" << endl; }
};

class UseResources
{
    class Cat cat;
    class Dog dog;

    public:
    UseResources() : cat(), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

int main()
{
    try
    {
        UseResources ur;
    }
    catch( int )
    {
        cout << "Inside handler" << endl;
    }
}

Ahora, si reemplazamos la definición del UseResources() constructor, con uno que usa un function try block, como a continuación,

UseResources() try : cat(), dog() { cout << "UseResources()" << endl; } catch(int) {}

la salida es la misma

Cat()  
Dog()  
~Cat()  
Inside handler 

es decir, con exactamente el mismo resultado final.

¿Qué es entonces, el propósito de un function try block ?

¿Fue útil?

Solución

Imaginar si UseResources se definió así:

class UseResources
{
    class Cat *cat;
    class Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { delete cat; cat = NULL; cout << "~UseResources()" << endl; }
};

Si Dog::Dog() lanza, entonces cat filtrará la memoria. Beato UseResourcesEl constructor nunca terminado, el objeto nunca se construyó completamente. Y por lo tanto no tiene su destructor llamado.

Para evitar esta fuga, debe usar un bloque de try/captura de nivel de función:

UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...)
{
  delete cat;
  throw;
}

Para responder a su pregunta más plenamente, el propósito de un bloque de try/captura a nivel de función en los constructores es específicamente para hacer este tipo de limpieza. Bloques de prueba/captura a nivel de función no poder Swallow Excepciones (las regulares pueden). Si atrapan algo, lo arrojarán nuevamente cuando lleguen al final del bloque de captura, a menos que lo vuelva a resultar explícitamente throw. Puedes transformar un tipo de excepción en otro, pero no puedes tragarla y seguir adelante como si no hubiera sucedido.

Esta es otra razón por la cual se deben usar valores y punteros inteligentes en lugar de punteros desnudos, incluso como miembros de la clase. Porque, como en su caso, si solo tiene valores de miembros en lugar de punteros, no tiene que hacer esto. Es el uso de un puntero desnudo (u otra forma de recurso no administrado en un objeto RAII) lo que obliga a este tipo de cosas.

Tenga en cuenta que este es más o menos el único uso legítimo de los bloques de prueba/captura de funciones.


Más razones para no usar los bloques de prueba de función. El código anterior se rompe sutilmente. Considera esto:

class Cat
{
  public:
  Cat() {throw "oops";}
};

Entonces, ¿qué pasa en UseResources¿El constructor? Bueno, la expresión new Cat lanzará, obviamente. Pero eso significa que cat nunca se inicializó. Lo que significa que delete cat producirá un comportamiento indefinido.

Puede intentar corregir esto usando un lambda complejo en lugar de solo new Cat:

UseResources() try
  : cat([]() -> Cat* try{ return new Cat;}catch(...) {return nullptr;} }())
  , dog()
{ cout << "UseResources()" << endl; }
catch(...)
{
  delete cat;
  throw;
}

Que teóricamente soluciona el problema, pero rompe un asumido invariante de UseResources. A saber, que UseResources::cat En todo momento será un puntero válido. Si eso es realmente un invariante de UseResources, entonces este código fallará porque permite la construcción de UseResources a pesar de la excepción.

Básicamente, no hay forma de hacer que este código sea seguro a menos que new Cat es noexcept (ya sea explícita o implícitamente).

Por el contrario, esto siempre funciona:

class UseResources
{
    unique_ptr<Cat> cat;
    Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

En resumen, busque un bloque de intento a nivel de función como un serio olor en código.

Otros consejos

Los bloques de la función de la función ordinaria tienen relativamente poco propósito. Son casi idénticos a un bloque de try dentro del cuerpo:

int f1() try {
  // body
} catch (Exc const & e) {
  return -1;
}

int f2() {
  try {
    // body
  } catch (Exc const & e) {
    return -1;
  }
}

La única diferencia es que el bloque de funciones de función vive en el choque de funciones ligeramente más grande, mientras que la segunda construcción vive en el cuerpo de funciones, el alcance anterior solo ve los argumentos de la función, el segundo también las variables locales (pero Esto no afecta las dos versiones de los bloques TRY).

La única aplicación interesante viene en un constructor-dry-block:

Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { }

Esta es la única forma en que se pueden atrapar las excepciones de uno de los inicializadores. No puedes resolver La excepción, ya que toda la construcción del objeto aún debe fallar (por lo tanto, debe salir del bloque de captura con una excepción, ya sea que desee o no). De todos modos, eso es La única forma de manejar las excepciones de la lista de inicializador específicamente.

¿Es esto útil? Probablemente no. Básicamente, no hay diferencia entre un bloque de try de constructor y el siguiente patrón de "inicializar a nulo y asignación" más típico, que en sí mismo es terrible:

Foo() : p1(NULL), p2(NULL), p3(NULL) {
  p1 = new Bar;
  try {
    p2 = new Zip;
    try {
      p3 = new Gulp;
    } catch (...) {
      delete p2;
      throw;
    }
  } catch(...) {
    delete p1;
    throw;
  }
}

Como puede ver, tiene un desastre indescriptible e indescriptible. Un constructor-try-block sería aún peor porque ni siquiera se podía saber cuántos de los punteros ya han sido asignados. Entonces realmente lo es solamente útil si tienes precisamente dos Asignaciones fugables. Actualizar: Gracias a la lectura esta pregunta Fui alertado del hecho de que en realidad No puede usar el bloque de captura para limpiar los recursos En absoluto, dado que referirse a objetos miembros es un comportamiento indefinido. Entonces [actualización final

En resumen: es inútil.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top