Reputation: 1143
Developing environment: GNU GCC (g++) 4.1.2
While I'm trying to investigate how to increase 'code coverage - particularly function coverage' in unit testing, I've found that some of class dtor seems to be generated multiple times. Does some of you have any idea on why, please?
I tried and observed what I mentioned the above by using the following code.
In "test.h"
class BaseClass
{
public:
~BaseClass();
void someMethod();
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass();
virtual void someMethod();
};
In "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;
}
When I built the above code (g++ test.cpp -o test) and then see what kind of symbols have been generated as follows,
nm --demangle test
I could see the following output.
==== 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()
My questions are as follows.
1) Why multiple dtors have been generated (BaseClass - 2, DerivedClass - 3)?
2) What are the difference among these dtors? How those multiple dtors will be selectively used?
I now have a feeling that in order to achieve 100% function coverage for C++ project, we would need to understand this so that I can invoke all those dtors in my unit tests.
I would greately appreciate if someone could give me the reply on the above.
Upvotes: 101
Views: 9930
Reputation: 231203
First, the purposes of these functions are described in the Itanium C++ ABI; see definitions under "base object destructor", "complete object destructor", and "deleting destructor". The mapping to mangled names is given in 5.1.4.
Basically:
operator delete
to actually free the memory.If you have no virtual base classes, D2 and D1 are identical; GCC will, on sufficient optimization levels, actually alias the symbols to the same code for both.
Upvotes: 87
Reputation: 29598
There are usually two variants of the constructor (not-in-charge / in-charge) and three of the destructor (not-in-charge / in-charge / in-charge deleting).
The not-in-charge ctor and dtor are used when handling an object of a class that inherits from another class using the virtual
keyword, when the object is not the complete object (so the current object is "not in charge" of constructing or destructing the virtual base object). This ctor receives a pointer to the virtual base object and stores it.
The in-charge ctor and dtors are for all the other cases, i.e. if there is no virtual inheritance involved; if the class has a virtual destructor, the in-charge deleting dtor pointer goes into the vtable slot, while a scope that knows the dynamic type of the object (i.e. for objects with automatic or static storage duration) will use the in-charge dtor (because this memory should not be freed).
Code example:
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);
Results:
foo
, baz
and quux
point at the respective in-charge deleting dtor.b1
and b2
are constructed by baz()
in-charge, which calls foo(1)
in-chargeq1
and q2
are constructed by quux()
in-charge, which falls foo(2)
in-charge and baz()
not-in-charge with a pointer to the foo
object it constructed earlierq2
is destructed by ~auto_ptr()
in-charge, which calls the virtual dtor ~quux()
in-charge deleting, which calls ~baz()
not-in-charge, ~foo()
in-charge and operator delete
.q1
is destructed by ~quux()
in-charge, which calls ~baz()
not-in-charge and ~foo()
in-chargeb2
is destructed by ~auto_ptr()
in-charge, which calls the virtual dtor ~baz()
in-charge deleting, which calls ~foo()
in-charge and operator delete
b1
is destructed by ~baz()
in-charge, which calls ~foo()
in-chargeAnyone deriving from quux
would use its not-in-charge ctor and dtor and take on the responsibility of creating the foo
object.
In principle, the not-in-charge variant is never needed for a class that has no virtual bases; in that case, the in-charge variant is then sometimes called unified, and/or the symbols for both in-charge and not-in-charge are aliased to a single implementation.
Upvotes: 37