Entorno de desarrollo: GNU GCC (g ++) 4.1.2
Mientras intento investigar cómo aumentar la 'cobertura de código, en particular la cobertura de funciones' en las pruebas unitarias, descubrí que parte de la clase dtor parece generarse varias veces. ¿Alguno de ustedes tiene alguna idea de por qué, por favor?
Intenté y observé lo que mencioné anteriormente usando 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 test) y luego veo qué tipo de símbolos se han generado de la siguiente manera,
nm - prueba de descomposición
Pude ver el siguiente resultado.
==== 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 han generado varios dtors (BaseClass - 2, DerivedClass - 3)?
2) ¿Cuáles son las diferencias entre estos dtores? ¿Cómo se utilizarán selectivamente esos múltiples dtores?
Ahora tengo la sensación de que para lograr una cobertura de funciones del 100% para el proyecto C ++, deberíamos entender esto para poder invocar todos esos dtors en mis pruebas unitarias.
Le agradecería mucho si alguien me pudiera dar la respuesta a lo anterior.
fuente
Respuestas:
Primero, los propósitos de estas funciones se describen en la ABI de Itanium C ++ ; consulte las definiciones en "destructor de objeto base", "destructor de objeto completo" y "destructor de eliminación". El mapeo a nombres mutilados se da en 5.1.4.
Básicamente:
operator delete
para liberar la memoria.Si no tiene clases de base virtuales, D2 y D1 son idénticas; GCC, en niveles de optimización suficientes, alias los símbolos con el mismo código para ambos.
fuente
struct B: virtual A
y luegostruct C: B
, al destruir unB
, invoca elB::D1
que a su vez invocaA::D2
y al destruir unC
, invoca elC::D1
que invocaB::D2
yA::D2
(observe cómoB::D2
no invoca un destructor). Lo realmente sorprendente de esta subdivisión es poder gestionar todas las situaciones con una jerarquía lineal simple de 3 destructores.Por lo general, hay dos variantes del constructor ( no a cargo / a cargo ) y tres del destructor ( no a cargo / a cargo / borrado a cargo ).
El ctor y dtor no a cargo se usan cuando se maneja un objeto de una clase que hereda de otra clase usando la
virtual
palabra clave, cuando el objeto no es el objeto completo (por lo que el objeto actual "no está a cargo" de construir o destruir el objeto base virtual). Este ctor recibe un puntero al objeto base virtual y lo almacena.El ctor y los dtors a cargo son para todos los demás casos, es decir, si no hay una herencia virtual involucrada; si la clase tiene un destructor virtual, el puntero dtor de eliminación encargado va a la ranura vtable, mientras que un osciloscopio que conoce el tipo dinámico del objeto (es decir, para objetos con duración de almacenamiento automático o estático) utilizará el dtor encargado (porque esta memoria no debe liberarse).
Ejemplo de código:
Resultados:
foo
,baz
yquux
punto en el respectivo a cargo de borrado DTOR.b1
yb2
están construidos porbaz()
encargado , que llamafoo(1)
encargadoq1
yq2
están construidos porquux()
el encargado , que caefoo(2)
a cargo ybaz()
no a cargo con un puntero alfoo
objeto que construyó anteriormenteq2
es destruido por~auto_ptr()
in-charge , que llama al dtor virtual~quux()
a cargo borrado , que llama~baz()
no-a-charge ,~foo()
in-charge yoperator delete
.q1
es destruido por~quux()
el encargado , que llama al~baz()
no responsable y~foo()
al responsableb2
es destruido por~auto_ptr()
el encargado , que llama al dtor virtual~baz()
a cargo borrar , que llama~foo()
al encargado yoperator delete
b1
es destruido por~baz()
el encargado , que llama~foo()
al encargadoCualquiera que se derive de
quux
usaría su no a cargo ctor y dtor y asumir la responsabilidad de crear elfoo
objeto.En principio, la variante sin cargo nunca es necesaria para una clase que no tiene bases virtuales; en ese caso, la variante a cargo a veces se denomina unificada , y / o los símbolos tanto para el responsable como para el no responsable se alias en una sola implementación.
fuente
delete
expresión como parte de su propio destructor o como parte de las llamadas al destructor del subobjeto. Ladelete
expresión se implementa como una llamada a través de vtable del objeto si tiene un destructor virtual (donde encontramos la eliminación a cargo , o como una llamada directa al destructor a cargo del objeto .delete
expresión nunca llama a la variante no responsable , que solo la usan otros destructores mientras destruyen un objeto que usa herencia virtual.