Environnement de développement: GNU GCC (g ++) 4.1.2
Alors que j'essaie d'étudier comment augmenter la `` couverture du code - en particulier la couverture des fonctions '' dans les tests unitaires, j'ai constaté qu'une partie de la classe dtor semble être générée plusieurs fois. Certains d'entre vous ont-ils une idée de pourquoi, s'il vous plaît?
J'ai essayé et observé ce que j'ai mentionné ci-dessus en utilisant le code suivant.
Dans "test.h"
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
Dans "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;
}
Quand j'ai construit le code ci-dessus (g ++ test.cpp -o test) et que j'ai vu le type de symboles générés comme suit,
nm - test de démangle
Je pouvais voir la sortie suivante.
==== 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()
Mes questions sont les suivantes.
1) Pourquoi plusieurs dtors ont-ils été générés (BaseClass - 2, DerivedClass - 3)?
2) Quelle est la différence entre ces dtors? Comment ces multiples détecteurs seront-ils utilisés de manière sélective?
J'ai maintenant le sentiment que pour atteindre une couverture de fonctions à 100% pour le projet C ++, nous aurions besoin de comprendre cela afin que je puisse invoquer tous ces dtors dans mes tests unitaires.
J'apprécierais beaucoup si quelqu'un pouvait me donner la réponse sur ce qui précède.
la source
Réponses:
Tout d'abord, les objectifs de ces fonctions sont décrits dans l' ABI Itanium C ++ ; voir les définitions sous "destructeur d'objet de base", "destructeur d'objet complet" et "suppression du destructeur". La correspondance avec les noms mutilés est donnée en 5.1.4.
Fondamentalement:
operator delete
à libérer la mémoire.Si vous n'avez pas de classes de base virtuelles, D2 et D1 sont identiques; GCC, à des niveaux d'optimisation suffisants, alias les symboles sur le même code pour les deux.
la source
struct B: virtual A
et puisstruct C: B
, alors lors de la destruction d'unB
vous invoquezB::D1
qui à son tour invoqueA::D2
et lors de la destruction d'unC
vous invoquezC::D1
qui invoqueB::D2
etA::D2
(notez commentB::D2
n'invoque pas un destructeur). Ce qui est vraiment étonnant dans cette subdivision, c'est de pouvoir réellement gérer toutes les situations avec une simple hiérarchie linéaire de 3 destructeurs.Il existe généralement deux variantes du constructeur ( non-responsable / responsable ) et trois du destructeur ( suppression non-responsable / responsable / responsable ).
Les ctor et dtor non en charge sont utilisés lors de la gestion d'un objet d'une classe qui hérite d'une autre classe à l'aide du
virtual
mot - clé, lorsque l'objet n'est pas l'objet complet (donc l'objet actuel n'est "pas chargé" de la construction ou de la destruction l'objet de base virtuel). Ce ctor reçoit un pointeur vers l'objet de base virtuel et le stocke.Le en charge sont pour tous les autres cas, par exemple , s'il n'y a pas d' héritage virtuel impliqué cteur et dtors; si la classe a un destructeur virtuel, le pointeur dtor de suppression en charge va dans le slot vtable, tandis qu'un scope qui connaît le type dynamique de l'objet (c'est-à-dire pour les objets avec une durée de stockage automatique ou statique) utilisera le dtor en charge (car cette mémoire ne doit pas être libérée).
Exemple de code:
Résultats:
foo
,baz
etquux
point respectif de suppression en charge dtor.b1
etb2
sont construits par lebaz()
responsable , qui appelle lefoo(1)
responsableq1
etq2
sont construits par lequux()
responsable , qui tombefoo(2)
en charge etbaz()
non en charge avec un pointeur vers l'foo
objet qu'il a construit précédemmentq2
est détruit par~auto_ptr()
in-charge , qui appelle le dtor virtuel~quux()
en charge la suppression , qui appelle~baz()
non-responsable ,~foo()
responsable etoperator delete
.q1
est détruit par le~quux()
responsable , qui appelle~baz()
non-responsable et~foo()
responsableb2
est détruit par le~auto_ptr()
responsable , qui appelle le dtor virtuel~baz()
en charge la suppression , qui appelle le~foo()
responsable etoperator delete
b1
est détruit par le~baz()
responsable , qui appelle le~foo()
responsableToute personne dérivant de
quux
utiliserait son ctor et dtor non-en charge et prendrait la responsabilité de créer l'foo
objet.En principe, la variante sans charge n'est jamais nécessaire pour une classe qui n'a pas de bases virtuelles; dans ce cas, la variante en charge est alors parfois appelée unifiée , et / ou les symboles à la fois en charge et non en charge sont aliasés sur une seule implémentation.
la source
delete
expression soit dans le cadre de votre propre destructeur, soit dans le cadre des appels du destructeur de sous-objet. L'delete
expression est implémentée soit en tant qu'appel via la table virtuelle de l'objet s'il possède un destructeur virtuel (où nous trouvons la suppression en charge , soit en tant qu'appel direct au destructeur en charge de l'objet .delete
expression n'appelle jamais la variante non chargée , qui n'est utilisée que par d'autres destructeurs lors de la destruction d'un objet qui utilise l'héritage virtuel.