2011-07-07 12 views
74

entorno de desarrollo: GNU GCC (g ++) 4.1.2GNU GCC (g ++): ¿Por qué genera múltiples dtors?

Mientras que estoy tratando de investigar cómo aumentar 'la cobertura de código - en especial la cobertura función' en la unidad de pruebas, he encontrado que algunos de dtor clase parece ser generado varias veces. ¿Alguno de ustedes tiene alguna idea sobre por qué, por favor?

me trataron y observaron lo que he mencionado anteriormente utilizando el siguiente código.

En "test.h"

class BaseClass 
{ 
public: 
    ~BaseClass(); 
    void someMethod(); 
}; 

class DerivedClass : public BaseClass 
{ 
public: 
    virtual ~DerivedClass(); 
    virtual void someMethod(); 
}; 

En "test.cpp"

#include <iostream> 
#include "test.h" 

BaseClass::~BaseClass() 
{ 
    std::cout << "BaseClass dtor invoked" << std::endl; 
} 

void BaseClass::someMethod() 
{ 
    std::cout << "Base class method" << std::endl; 
} 

DerivedClass::~DerivedClass() 
{ 
    std::cout << "DerivedClass dtor invoked" << std::endl; 
} 

void DerivedClass::someMethod() 
{ 
    std::cout << "Derived class method" << std::endl; 
} 

int main() 
{ 
    BaseClass* b_ptr = new BaseClass; 
    b_ptr->someMethod(); 
    delete b_ptr; 
} 

Cuando construí el código anterior (g ++ test.cpp -o prueba) y luego ver qué tipo de símbolos se han generado como sigue,

nm prueba --demangle

pude ver la siguiente salida.

==== following is partial output ==== 
08048816 T DerivedClass::someMethod() 
08048922 T DerivedClass::~DerivedClass() 
080489aa T DerivedClass::~DerivedClass() 
08048a32 T DerivedClass::~DerivedClass() 
08048842 T BaseClass::someMethod() 
0804886e T BaseClass::~BaseClass() 
080488f6 T BaseClass::~BaseClass() 

Mis preguntas son las siguientes.

1) ¿Por qué se generaron varios controladores (BaseClass - 2, DerivedClass - 3)?

2) ¿Cuáles son la diferencia entre estos dtors? ¿Cómo se usarán esos dtors múltiples de forma selectiva?

Ahora tengo la sensación de que para lograr un 100% de cobertura de funciones para el proyecto C++, tendríamos que entender esto para poder invocar todos esos controladores en mis pruebas unitarias.

apreciaría greately si alguien me podría dar la respuesta a lo anterior.

+4

+1 para incluir un programa de muestra mínimo y completo. (http://sscce.org) –

+2

¿Su clase base tiene intencionalmente un destructor no virtual? –

+2

Una pequeña observación; has pecado y no has hecho que tu destructor BaseClass sea virtual. – Lyke

Respuesta

56

En primer lugar, los efectos de estas funciones se describen en la Itanium C++ ABI; vea las definiciones en "destructor de objetos base", "destructor de objetos completos" y "eliminación de destructor". El mapeo de nombres destrozados se da en 5.1.4.

Básicamente:

  • D2 es el "objeto destructor base". Destruye el objeto en sí, así como los miembros de datos y las clases base no virtuales.
  • D1 es el "destructor de objeto completo". Además, destruye las clases base virtuales.
  • D0 es el "destructor de objeto eliminado". Hace todo lo que hace el destructor de objetos completo, y llama al operator delete para liberar la memoria.

Si no tiene clases base virtuales, D2 y D1 son idénticos; GCC, en niveles de optimización suficientes, alias los símbolos del mismo código para ambos.

+0

Gracias por la respuesta clara. Ahora que me puedo identificar, aunque necesito estudiar más ya que no estoy tan familiarizado con el tipo de material de herencia virtual. – Smg

+0

@Smg: en herencia virtual, las clases heredadas "virtualmente" están bajo la exclusiva responsabilidad del objeto más derivado. Es decir, si tienes 'struct B: virtual A' y luego' struct C: B', entonces al destruir una 'B' invocas' B :: D1' que a su vez invoca 'A :: D2' y al destruir una 'C' invoca' C :: D1' que invoca 'B :: D2' y' A :: D2' (observe cómo 'B :: D2' no invoca A destructor). Lo realmente sorprendente de esta subdivisión es poder gestionar todas las situaciones con una simple jerarquía lineal de destructores * 3 *. –

+0

Hmm, puede que no haya entendido el punto claramente ... pensé que en el primer caso (destrucción del objeto B), se invocará A :: D1 en lugar de A :: D2.Y también en el segundo caso (destrucción del objeto C), se invocará A :: D1 en lugar de A :: D2. ¿Me equivoco? – Smg

31

por lo general hay dos variantes del constructor (no a cargo/a cargo) y tres del destructor (no a cargo/a cargo/in- carga eliminando).

El no a cargo ctor y DTOR se utilizan cuando el manejo de un objeto de una clase que hereda de otra clase usando la palabra clave virtual, cuando el objeto no es el objeto completo (lo que el objeto actual es "no a cargo "de construir o destruir el objeto base virtual"). Este controlador recibe un puntero al objeto base virtual y lo almacena.

controladores y controladores son para todos los demás casos, es decir, si no hay una herencia virtual involucrada; si la clase tiene un destructor virtual, borrando el puntero dtor entra en la ranura vtable, mientras que un alcance que conoce el tipo dinámico del objeto (es decir, para objetos con duración de almacenamiento automática o estática) usará en -charge dtor (porque esta memoria no se debe liberar).

ejemplo Código:

struct foo { 
    foo(int); 
    virtual ~foo(void); 
    int bar; 
}; 

struct baz : virtual foo { 
    baz(void); 
    virtual ~baz(void); 
}; 

struct quux : baz { 
    quux(void); 
    virtual ~quux(void); 
}; 

foo::foo(int i) { bar = i; } 
foo::~foo(void) { return; } 

baz::baz(void) : foo(1) { return; } 
baz::~baz(void) { return; } 

quux::quux(void) : foo(2), baz() { return; } 
quux::~quux(void) { return; } 

baz b1; 
std::auto_ptr<foo> b2(new baz); 
quux q1; 
std::auto_ptr<foo> q2(new quux); 

Resultados:

  • La entrada DTOR en cada uno de los vtables para foo, baz y quux punto en la respectiva a cargo eliminar DTOR.
  • b1 y b2 están construidos por baz()a cargo, que llama a foo(1)a cargo
  • q1 y q2 están construidos por quux()a cargo, que cae foo(2)a cargo y baz()no a cargo con un puntero al objeto foo que construyó anteriormente
  • q2 se destruye por ~auto_ptr()a cargo, que llama al dtor virtuales ~quux()a cargo de eliminar, que llama ~baz()no a cargo, ~foo()a cargo y operator delete.
  • q1 se destruye por ~quux()a cargo, que llama ~baz()no a cargo y ~foo()a cargo
  • b2 se destruye por ~auto_ptr()a cargo, que llama al dtor virtuales ~baz()a cargo de eliminar, que llama ~foo()a cargo y operator delete
  • b1 se destruye por ~baz()a cargo, que llama ~foo()a cargo

Cualquier persona que deriva de quux utilizaría su no a cargo ctor y dtor y asumir la responsabilidad de creando el objeto foo.

En principio, la variante sin cargo nunca es necesaria para una clase que no tiene bases virtuales; en ese caso, la variante a cargo es entonces llama a veces unificado, y/o los símbolos de ambos a cargo y no a cargo son alias a una única aplicación.

+0

Gracias por su explicación clara junto con un ejemplo bastante fácil de entender. En caso de que se trate de una herencia virtual, la responsabilidad de la clase más derivada es crear un objeto de clase base virtual. En cuanto a las otras clases que la clase más derivada, se supone que deben ser interpretadas por un constructor que no está a cargo, por lo que no tocan la clase base virtual. – Smg

Cuestiones relacionadas