Ambiente de desenvolvimento: GNU GCC (g ++) 4.1.2
Enquanto estou tentando investigar como aumentar a 'cobertura de código - particularmente a cobertura de função' em testes de unidade, descobri que parte da classe dtor parece ser gerada várias vezes. Alguns de vocês têm ideia do porquê, por favor?
Eu tentei e observei o que mencionei acima usando o código a seguir.
Em "test.h"
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
Em "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;
}
Quando eu construí o código acima (g ++ test.cpp -o test) e vejo que tipo de símbolos foram gerados da seguinte maneira,
nm - teste demangle
Eu pude ver a seguinte saída.
==== 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()
Minhas perguntas são as seguintes.
1) Por que vários dtors foram gerados (BaseClass - 2, DerivedClass - 3)?
2) Quais são as diferenças entre esses dtores? Como esses múltiplos dtors serão usados seletivamente?
Agora tenho a sensação de que, para atingir 100% de cobertura de função para o projeto C ++, precisaríamos entender isso para que eu possa invocar todos aqueles dtors em meus testes de unidade.
Eu apreciaria muito se alguém pudesse me dar a resposta sobre o acima.
fonte
Respostas:
Primeiro, as finalidades dessas funções são descritas no Itanium C ++ ABI ; consulte as definições em "destruidor de objeto básico", "destruidor de objeto completo" e "excluindo destruidor". O mapeamento para nomes mutilados é fornecido em 5.1.4.
Basicamente:
operator delete
para realmente liberar a memória.Se você não tem classes base virtuais, D2 e D1 são idênticos; O GCC irá, em níveis de otimização suficientes, criar um alias dos símbolos para o mesmo código para ambos.
fonte
struct B: virtual A
e entãostruct C: B
, então ao destruir umB
você invocaB::D1
que por sua vez invocaA::D2
e ao destruir umC
você invocaC::D1
que invocaB::D2
eA::D2
(observe comoB::D2
não invoca um destruidor). O que realmente é incrível nesta subdivisão é ser capaz de gerenciar todas as situações com uma hierarquia linear simples de 3 destruidores.Normalmente, há duas variantes do construtor ( não responsável / responsável ) e três do destruidor ( exclusão não responsável / responsável / responsável ).
O ctor e dtor não-responsáveis são usados ao manusear um objeto de uma classe que herda de outra classe usando a
virtual
palavra - chave, quando o objeto não é o objeto completo (então o objeto atual "não está encarregado" de construir ou destruir o objeto de base virtual). Este ctor recebe um ponteiro para o objeto de base virtual e o armazena.O encarregado ctor e dtors são para todos os outros casos, ou seja, se não houver nenhuma herança virtual envolvidos; se a classe tiver um destruidor virtual, o ponteiro responsável pela exclusão do dtor vai para o slot vtable, enquanto um escopo que conhece o tipo dinâmico do objeto (ou seja, para objetos com duração de armazenamento automático ou estático) usará o dtor responsável (porque essa memória não deve ser liberada).
Exemplo de código:
Resultados:
foo
,baz
equux
ponto na respectiva exclusão em-carga dtor.b1
eb2
são construídos pelobaz()
responsável , que liga ofoo(1)
responsávelq1
eq2
são construídos peloquux()
responsável , que ficafoo(2)
responsável ebaz()
não responsável por um ponteiro para ofoo
objeto que ele construiu anteriormenteq2
é destruído pelo~auto_ptr()
responsável , que chama o dtor virtual~quux()
responsável pela exclusão , que chama o~baz()
não responsável , o~foo()
responsável eoperator delete
.q1
é destruído pelo~quux()
responsável , que liga para o~baz()
não-responsável e o~foo()
responsávelb2
é destruído pelo~auto_ptr()
responsável , que chama o dtor virtual~baz()
responsável pela exclusão , que chama o~foo()
responsável eoperator delete
b1
é destruído pelo~baz()
responsável , que liga o~foo()
responsávelQualquer derivado de
quux
usaria seu ctor e dtor não-responsáveis e assumiria a responsabilidade de criar ofoo
objeto.Em princípio, a variante sem carga nunca é necessária para uma classe que não possui bases virtuais; nesse caso, a variante responsável é então às vezes chamada de unificada e / ou os símbolos para responsável e não-responsável são aliasados para uma única implementação.
fonte
delete
expressão como parte de seu próprio destruidor ou como parte das chamadas do destruidor de subobjeto. Adelete
expressão é implementada como uma chamada por meio da vtable do objeto se ele tiver um destruidor virtual (onde encontramos a exclusão responsável , ou como uma chamada direta ao destruidor responsável pelo objeto .delete
expressão nunca chama a variante não responsável , que é usada apenas por outros destruidores enquanto destrói um objeto que usa herança virtual.