Pregunta GNU GCC (g ++): ¿Por qué genera múltiples dtors?


Entorno de desarrollo: GNU GCC (g ++) 4.1.2

Mientras trato de investigar cómo aumentar la cobertura del código, particularmente la cobertura de funciones, en las pruebas unitarias, descubrí que algunas de las clases parecen generarse varias veces. ¿Alguno de ustedes tiene alguna idea sobre 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 detección

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 múltiples dtors (BaseClass - 2, DerivedClass - 3)?

2) ¿Cuál es 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 una cobertura 100% funcional para el proyecto C ++, tendríamos que entender esto para poder invocar todos esos dtors en mis pruebas unitarias.

Agradecería enormemente si alguien pudiera darme la respuesta sobre lo anterior.


76
2017-07-07 16:24


origen


Respuestas:


Primero, los propósitos de estas funciones se describen en 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 "destructor de objetos base". Destruye el objeto en sí, así como los miembros de datos y las clases base no virtuales.
  • D1 es el "destructor de objetos 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, además de llamadas operator delete para realmente 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.


58
2017-07-07 17:06



Generalmente hay dos variantes del constructor (no a cargo / en cargo) y tres del destructor (no a cargo / en cargo / eliminación a cargo)

los no a cargo ctor y dtor se usan cuando se maneja un objeto de una clase que hereda de otra clase usando el 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 controlador recibe un puntero al objeto base virtual y lo almacena.

los en cargo ctor y dtors son para todos los demás casos, es decir, si no hay herencia virtual involucrada; si la clase tiene un destructor virtual, el eliminación a cargo El puntero dtor entra en la ranura de vtable, mientras que un ámbito que conoce el tipo dinámico del objeto (es decir, para objetos con duración de almacenamiento automática o estática) usará el en cargo dtor (porque esta memoria no debe liberarse).

Ejemplo de 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 el respectivo eliminación a cargo dtor.
  • b1 y b2 son construidos por baz()  en cargo, que llama foo(1)  en cargo
  • q1 y q2 son construidos por quux()  en cargo, que cae foo(2)  en cargo y baz()  no a cargo con un puntero a la foo objeto que construyó antes
  • q2 es destruido por ~auto_ptr()  en cargo, que llama al dtor virtual ~quux()  eliminación a cargo, que llama ~baz()  no a cargo, ~foo()  en cargo y operator delete.
  • q1 es destruido por ~quux()  en cargo, que llama ~baz()  no a cargo y ~foo()  en cargo
  • b2 es destruido por ~auto_ptr()  en cargo, que llama al dtor virtual ~baz()  eliminación a cargo, que llama ~foo()  en cargo y operator delete
  • b1 es destruido por ~baz()  en cargo, que llama ~foo()  en cargo

Cualquiera derivado de quux usaría su no a cargo ctor y dtor y asumir la responsabilidad de crear el foo objeto.

En principio, el no a cargo la variante nunca es necesaria para una clase que no tiene bases virtuales; en ese caso, el en cargo La variante se llama a veces unificadoy / o los símbolos para ambos en cargo y no a cargo tienen un alias para una sola implementación.


33
2017-07-07 17:48