2011-08-05 18 views
6

excepcional C++ menciona el siguiente códigoC++ seguridad excepción

template <class T> class Stack 
{ 
    public: 
     Stack(); 
     ~Stack(); 

     /*...*/ 

    private: 
     T*  v_;  // ptr to a memory area big 
     size_t vsize_; // enough for 'vsize_' T's 
     size_t vused_; // # of T's actually in use 
}; 



template<class T> 
Stack<T>::Stack() 
     : v_(new T[10]), // default allocation 
      vsize_(10), 
      vused_(0)  // nothing used yet 
{ 
} 

Se dice que si uno de los constructores T lanzó, entonces cualquier objeto T que fueron totalmente construidas fueron destruidos adecuadamente y, por último, operador de eliminación se llama automáticamente para liberar la memoria. Eso nos hace a prueba de fugas.

Según entendí, si un constructor lanza una excepción, la aplicación debería limpiar los recursos asignados. ¿Cómo es el anterior a prueba de fugas?

Respuesta

8

Citando el estándar C++ 03, §5.3.4/8:

A nueva expresión obtiene de almacenamiento para el objeto llamando a una función de asignación .Si la nueva expresión finaliza lanzando una excepción, puede liberar almacenamiento llamando a una función de desasignación. Si el tipo asignado es un tipo que no es de matriz, el nombre de la función de asignación es operator new y el nombre de la función de desasignación es operator delete. Si el tipo asignado es un tipo de matriz, el nombre de la función de asignación es operator new[] y el nombre de la función de desasignación es operator delete[].

§5.3.4/17:

Si cualquier parte de la inicialización objeto descrito anteriormente termina por lanzar una excepción y una función de cancelación de asignación adecuados se pueden encontrar, la función de cancelación de asignación se llama para liberar el memoria en la que se estaba construyendo el objeto, después de lo cual la excepción continúa propagándose en el contexto de new-expression.

En consecuencia, en su caso T constructor lanza una excepción, el tiempo de ejecución destruirá cualquier sub-objetos ya creados de la T instancia cuyo constructor lanzó, a continuación, llamar operator delete[] en la matriz como un todo, la destrucción de los elementos ya creados y desasignando la memoria de la matriz.

+0

Hace que se pregunte qué tan bien funcionan las implementaciones: si el constructor para 'v_ [5]' arrojó, entonces 'eliminaría v _ []' solo destruiría 'v_ [0]' a través de 'v_ [4]', y saltaría las no construido? –

+0

@Mike: los que no están construidos no necesitan ser destruidos (nunca fueron creados); ¿qué estás diciendo que es el problema? – ildjarn

+0

Me pregunto si va y los destruye de todos modos. (Es decir, ¿pierde la pista de cuántos construyó con éxito?) Ahora que lo pienso, me pregunto si garantiza la destrucción en el orden inverso de la construcción ... –

3

[Corrección:] No lo es. Una excepción en su constructor no perderá recursos porque el único lugar donde podría ocurrir una excepción es dentro de la expresión new, y si falla una expresión new, se liberan los recursos asignados por ella. Su situación es especial porque solo realiza una única asignación en el constructor; en general, esto no es seguro.

Su frase citada se refiere a es el operador de eliminación para el objeto cuyo constructor no arrojó:

struct T 
{ 
    T() { throw 1; } 
    char data[200]; 
}; 

// in your code: 

T * pt = new T; 

En la última línea, se asigna memoria antes de que se invoca el constructor. Esa memoria se libera en caso de una excepción, mediante una llamada automática al ::operator delete(pt). (En general, el juego delete-operador (no "expresión") acorde con la nueva expresión se llama!).

Dice así:

  • construcción exitosa: 1. Asignación. 2. Construcción. 3. Destrucción. 4. Desasignación.

  • Construcción no exitosa: 1. Asignación. 2. Desasignación.

en cuenta que sólo tenemos un objeto después de ha completado el constructor - por lo que en caso de una excepción en el constructor, que ni siquiera tienen un objeto. Es por eso que dije "fall-object" arriba con un guión, porque no es un objeto en absoluto (como el abeto Douglas no es un abeto en absoluto).

su código es potencialmente inseguro por completo fugas , si va a realizar más de una asignación que podría lanzar - es decir, una fuga ocurre cuando un objeto se ha construido con éxito pero otro, uno de fallar. Probablemente debería simplemente no llamar new en la lista de inicialización y en lugar de ponerlo en el cuerpo:

class Danger 
{ 
    T * pt1, * pt2; 
public: 
    Danger() 
    { 
    try { pt1 = new T; } catch(...) { throw(); } 
    try { pt2 = new T; } catch(...) { delete pt1; throw(); } 
    } 
}; 

O, por el principio de la responsabilidad individual, no utilizar punteros primas, pero la gestión de contenedores uso de los recursos que limpiar después de ¡¡sí mismos!!

+2

Estoy totalmente en desacuerdo, pero dudo de votar porque generalmente eres un tipo bastante inteligente. ; -] A la luz del standardese citado en mi respuesta, ¿realmente crees que el código del OP puede tener fugas? – ildjarn

+0

No entiendo cómo se está destruyendo los datos de char []. –

+0

@ildjarn: verifique mi actualización; el código real en el OP es posiblemente correcto, pero tan pronto como agregue algo al constructor que pueda arrojar, tendrá problemas. Lo único que está protegido contra fugas es el objeto en sí. –

0

Pruebe el puntero automático para T* v_ o cualquier recurso dinámico asignado. Si ocurre lo siguiente

template<class T> 
Stack<T>::Stack() 
     : v_(new T[10]), 
      vsize_(10), 
      vused_(0) 
{ 
    throw 0; // terminated by exception 
} 

o, hay otro objeto en el Stack una excepción cuando se construye, v_ causará pérdida de memoria. Si lo ajusta como std::unique_ptr<T[]> v_ o algo así, se liberará automáticamente si la construcción de Stack finalizó con una excepción.

+0

Si con "puntero automático" te refieres a 'std :: auto_ptr', usar eso con una matriz invoca un comportamiento indefinido porque llama a' eliminar' en lugar de a 'eliminar []'. – ildjarn

+0

@ildjarn Disculpe "auto" aquí significa administración automática de recursos, no 'std :: auto_ptr'. Como ve, uso 'std :: unique_ptr' en mi consejo, ¿verdad? – neuront

+0

De hecho, solo quise aclarar; Ciertamente no tengo ningún problema para recomendar 'unique_ptr'. : -] – ildjarn

Cuestiones relacionadas