2008-09-29 14 views
18
class someclass {}; 

class base 
{ 
    int a; 
    int *pint; 
    someclass objsomeclass; 
    someclass* psomeclass; 
public: 
    base() 
    { 
     objsomeclass = someclass(); 
     psomeclass = new someclass(); 
     pint = new int(); 
     throw "constructor failed"; 
     a = 43; 
    } 
} 

int main() 
{ 
    base temp(); 
} 

En el código anterior, el constructor lanza. ¿Qué objetos se filtrarán y cómo pueden evitarse las pérdidas de memoria?¿El código siguiente causará pérdida de memoria en C++

int main() 
{ 
    base *temp = new base(); 
} 

¿Qué tal en el código de arriba? ¿Cómo se pueden evitar las pérdidas de memoria después del lanzamiento del constructor?

+1

Lo sé, tengo una naturaleza horrible, que no puedo resistir a nitpick. No puedo evitarlo Mis 2 centavos: declaración objsomeclass = someclass(); es innecesario En el cuerpo del constructor, objsomeclass ya está inicializado por defecto. objsomeclass (someclass()) a continuación tampoco tiene sentido. –

+0

Estoy de acuerdo, pero creo que someclass tiene un constructor explícito. Y yo quería centrarme en el objeto creado en el constructor – yesraaj

+0

Sí, sé que es solo un ejemplo. Es por eso que lo llamé quisquilloso. BTW constructor base() podría ser público :) –

Respuesta

35

Sí, perderá memoria. Cuando se lanza el constructor, no se llamará a ningún destructor (en este caso, no se mostrará un destructor que libere los objetos asignados dinámicamente, pero supongamos que tiene uno).

Esta es una razón importante para usar punteros inteligentes: como los poitners inteligentes son objetos de pleno derecho, recibirán destructores durante el desenrollado de la pila de la excepción y tendrán la oportunidad de liberar la memoria.

Si utiliza algo así como scoped_ptr <> plantilla de Impulso, su clase podría parecerse más a:

class base{ 
    int a; 
    scoped_ptr<int> pint; 
    someclass objsomeclass; 
    scoped_ptr<someclass> psomeclass; 
    base() : 
     pint(new int), 
     objsomeclass(someclass()), 
     psomeclass(new someclass()) 

    { 
     throw "constructor failed"; 
     a = 43; 
    } 
} 

Y usted no tendría pérdidas de memoria (y el dtor predeterminado también limpiaría las asignaciones de memoria dinámica) .


En resumen (y esperemos que esto también responde a la pregunta acerca de la declaración

base* temp = new base(); 

):

Cuando se produce una excepción dentro de un constructor, hay varias cosas que se deben tener nota de en términos de manejo adecuado de las asignaciones de recursos que pueden haber ocurrido en la construcción abortada del objeto:

  1. se llamará el destructor para el objeto que se está construyendo no.
  2. destructores de objetos miembro contenidos en la clase de ese objeto serán llamados
  3. la memoria para el objeto que se está construyendo será liberado.

Esto significa que si su objeto es propietaria de los recursos, que tiene 2 métodos disponibles para limpiar los recursos que podrían ya han sido adquiridos cuando el constructor lanza:

  1. captura la excepción, liberar los recursos, luego vuelve a tirar Esto puede ser difícil de corregir y puede convertirse en un problema de mantenimiento.
  2. usan objetos para administrar los tiempos de vida de los recursos (RAII) y usan esos objetos como miembros. Cuando el constructor de su objeto arroja una excepción, se llamará a los objetos miembros y tendrá la oportunidad de liberar el recurso cuyas vidas son las responsables.
+0

¿No está cargando en Boost, solo consigue la administración de memoria bastante tonta? –

+0

Quizás, pero scoped_ptr está en TR1 y estará en C++ 09, por lo que es algo que debería aprenderse de todos modos. Y la parte de Boost que tiene scoped_ptr es solo un montón de encabezados. Finalmente, podría usar auto_ptr para este simple ejemplo, pero auto_ptr es probablemente algo que debería evitarse. –

+0

¿se llamará el dtor de la clase base incluso si tengo una vez? lo que sucederá con la línea siguiente base * temp = new base(); – yesraaj

-2

Todo lo que "nuevo" debe eliminarse, o provocará una pérdida de memoria. Por lo que estas dos líneas:

psomeclass = new someclass(); 
pint = new int(); 

causará pérdidas de memoria, debido a que tiene que hacer:

delete pint; 
delete psomeclass; 

En un bloque finally para evitar que sean filtrados.

Además, esta línea:

base temp = base(); 

es innecesario. Solo tiene que hacer:

base temp; 

No es necesario agregar "= base()".

+1

No existe un bloque "final" en C++ –

+0

Es cierto que puede o no tener acceso a él dependiendo de su sabor C++ - si no , deberá asegurarse de que las asignaciones se eliminen independientemente de la ruta de acceso del código. – Colen

+1

Su observación sobre la inicialización adicional es incorrecta. El objeto resultante solo se inicializará una vez y no se copiará. –

0

Sí, ese código perderá memoria. Los bloques de memoria asignados usando "nuevo" no se liberan cuando se produce una excepción. Esto es parte de la motivación detrás de RAII.

Para evitar la pérdida de memoria, intentar algo como esto:

psomeclass = NULL; 
pint = NULL; 
/* So on for any pointers you allocate */ 

try { 
    objsomeclass = someclass(); 
    psomeclass = new someclass(); 
    pint = new int(); 
    throw "constructor failed"; 
    a = 43; 
} 
catch (...) 
{ 
    delete psomeclass; 
    delete pint; 
    throw; 
} 

+0

en lugar de usar punteros utilizando objetos (puntero inteligente), mejorará las cosas. Puesto que cada vez que se lanza una excepción en un bloque, se borran los objetos automáticos. – yesraaj

+0

punteros inteligentes son mejores. También reemplace 'raise'; con 'throw;' Para volver a lanzar la excepción actual. –

0

Si se lanza en un constructor, debe limpiar todo lo que vino antes de la llamada a tirar. Si estás usando herencia o lanzando un destructor, realmente no deberías. El comportamiento es extraño (no tengo mi estándar a mano, pero podría no estar definido?).

+0

No estoy seguro de si realmente no está definido, pero es ciertamente muy peligroso porque se invocan destructores durante el desenrollado de la pila en el caso de una excepción planteada. Si genera una excepción * mientras * otro se ha generado, cada tiempo de ejecución de C++ que conozco terminará la aplicación. –

+0

Una excepción no detectada en un destructor generado durante el manejo de excepciones hace que se llame a std :: terminate(), que de manera predeterminada llama a std :: abort(). El comportamiento predeterminado puede ser anulado. – KTC

+0

aunque el comportamiento predeterminado puede ser anulado, su versión aún no puede regresar a la aplicación, todavía tiene que salir. –

5

Ambas nuevas se filtrarán.

asignar la dirección de los objetos del montón creado para nombrados punteros inteligentes para que se borrará en el interior del destructor punteros inteligentes que consiguen llamar cuando se produce la excepción - (RAII).

class base { 
    int a; 
    boost::shared_ptr<int> pint; 
    someclass objsomeclass; 
    boost::shared_ptr<someclass> psomeclass; 

    base() : 
     objsomeclass(someclass()), 
     boost::shared_ptr<someclass> psomeclass(new someclass()), 
     boost::shared_ptr<int> pint(new int()) 
    { 
     throw "constructor failed"; 
     a = 43; 
    } 
}; 

ahora psomeclass & pinta destructores serán llamados cuando la pila relajarse cuando la excepción se produce en el constructor, y los destructores se desasignar la memoria asignada.

int main(){ 
    base *temp = new base(); 
} 

Para la asignación de memoria ordinaria usando (no plcaement) nueva de memoria asignada por el operador de nuevo se libera automáticamente si el constructor lanza una excepción. En términos de por qué molestarse en liberar miembros individuales (en respuesta a los comentarios a la respuesta de Mike B), la liberación automática solo se aplica cuando se lanza una excepción en un constructor de un objeto que se está asignando recientemente, no en otros casos. Además, la memoria que se libera es la asignada para los miembros del objeto, no cualquier memoria que haya asignado dentro del constructor. es decirSería liberar la memoria para las variables miembro una pinta, , objsomeclass y psomeclass, pero no la memoria asignada a nueva someclass() y new int().

+0

shared_ptr <> es exagerado si posee el objeto y nunca va a otorgar la propiedad compartida.Simplifique con std :: auto_ptr <> –

+0

// Cambié la pregunta para tener base * temp = new base(); – yesraaj

+0

Y boost :: scoped_ptr <> podría ser incluso mejor que auto_ptr <> que tiene su propia lata de gusanos. –

-3

necesita eliminar psomeclass ... No es necesario limpiar el entero ...

RWendi

+0

¿Puedes por favor elaborar Dave Moore? ¿Se trata de la parte "no es necesario limpiar el entero"? La razón detrás de esto es que el puntero de la memoria Int no cuesta mucho en comparación con el puntero de la memoria de clase, es por eso que dije que no era necesario limpiarlo. – RWendi

+0

Ambos tienen fugas; el costo no es un problema La pregunta era si se filtró o no. Y si ese trozo de código se ejecuta miles o millones de veces, ese pequeño costo se acumula. Incluso si el "costo" fuera relevante, no es el tamaño del * puntero * lo que hace la diferencia, sino el tamaño de la entidad apuntada. Por ejemplo, es posible para sizeof (someclass) == sizeof (int). Y no está borrando el puntero, está borrando la entidad apuntada. –

1

Creo que la respuesta más común es equivocado y todavía perder memoria. Se llamará al destructor para los miembros de la clase no si el constructor lanza una excepción (porque nunca completó su inicialización, y tal vez algunos miembros nunca han llegado a sus llamadas al constructor). Sus destructores solo se invocan durante la llamada al destructor de la clase. Eso solo tiene sentido.

Este sencillo programa lo demuestra.

#include <stdio.h> 


class A 
{ 
    int x; 

public: 
    A(int x) : x(x) { printf("A constructor [%d]\n", x); } 
    ~A() { printf("A destructor [%d]\n", x); } 
}; 


class B 
{ 
    A a1; 
    A a2; 

public: 
    B() 
    : a1(3), 
     a2(5) 
    { 
     printf("B constructor\n"); 
     throw "failed"; 
    } 
    ~B() { printf("B destructor\n"); } 
}; 


int main() 
{ 
    B b; 

    return 0; 
} 

Con la siguiente salida (usando g ++ 4.5.2):

A constructor [3] 
A constructor [5] 
B constructor 
terminate called after throwing an instance of 'char const*' 
Aborted 

Si su constructor falla a la mitad, entonces es su responsabilidad para tratar con él. Peor aún, ¡la excepción puede ser lanzada desde el constructor de su clase base! La forma de tratar estos casos es empleando un "bloque de prueba de función" (pero incluso entonces debe codificar cuidadosamente la destrucción de su objeto parcialmente inicializado).

El enfoque correcto para su problema sería entonces algo como esto:

#include <stdio.h> 


class A 
{ 
    int x; 

public: 
    A(int x) : x(x) { printf("A constructor [%d]\n", x); } 
    ~A() { printf("A destructor [%d]\n", x); } 
}; 


class B 
{ 
    A * a1; 
    A * a2; 

public: 
    B() 
    try // <--- Notice this change 
    : a1(NULL), 
     a2(NULL) 
    { 
     printf("B constructor\n"); 
     a1 = new A(3); 
     throw "fail"; 
     a2 = new A(5); 
    } 
    catch (...) { // <--- Notice this change 
     printf("B Cleanup\n"); 
     delete a2; // It's ok if it's NULL. 
     delete a1; // It's ok if it's NULL. 
    } 

    ~B() { printf("B destructor\n"); } 
}; 


int main() 
{ 
    B b; 

    return 0; 
} 

Si lo ejecuta usted conseguirá el resultado esperado en el que sólo los objetos asignados se destruyen y se liberaron.

B constructor 
A constructor [3] 
B Cleanup 
A destructor [3] 
terminate called after throwing an instance of 'char const*' 
Aborted 

Aún puede trabajar con punteros inteligentes compartidos si lo desea, con copia adicional. Escribir un constructor similar a esto:

class C 
{ 
    std::shared_ptr<someclass> a1; 
    std::shared_ptr<someclass> a2; 

public: 
    C() 
    { 
     std::shared_ptr<someclass> new_a1(new someclass()); 
     std::shared_ptr<someclass> new_a2(new someclass()); 

     // You will reach here only if both allocations succeeded. Exception will free them both since they were allocated as automatic variables on the stack. 
     a1 = new_a1; 
     a2 = new_a2; 
    } 
} 

Buena suerte, Tzvi.

+0

La excepción en su primer ejemplo no se detecta, por lo que no se desenrolla la pila y no se invocan destructores. Si ajusta 'B b;' en una captura de prueba, los destructores se llaman como se esperaba. –

Cuestiones relacionadas