why virtual base dtor gets called?

T

tuvok

Is it correct that the virtual dtor of base gets called implicitly?
Here's some code to demonstrate what I mean:
Class B has a virtual destructor, so has class D which
is derived from B. Deleting D calls the dtor of D and
then the dtor of B.
I was thinking that this would be true only for non-virtual dtor case,
but I wouldn't have expected it happen for a virtual dtor.
For a class with a virtual dtor I would have expected that only
the dtor of D would be called when D gets deleted.
What's correct? Is maybe my compiler buggy?

class B
{
public:
B() {}
virtual ~B() { std::cout << "~B\n"; }
};

class D : public B
{
public:
D() {}
virtual ~D() { std::cout << "~D\n"; }
};

void virtual_dtor_tester()
{
B b;
D d;
}

/* output:
~D
~B
~B
*/
 
S

Stephen Howe

For a class with a virtual dtor I would have expected that only
the dtor of D would be called when D gets deleted.

Nope. Since D is a B, the base part of D must be destroyed, hence ~B() is
called.
That is always true regardless of whether B or D contains virtual functions.

Compiler is correct.

Stephen Howe
 
P

Peter Julian

tuvok said:
Is it correct that the virtual dtor of base gets called implicitly?

Not really, the virtual d~tor of base class is invoked, not called.
Here's some code to demonstrate what I mean:
Class B has a virtual destructor, so has class D which
is derived from B. Deleting D calls the dtor of D and
then the dtor of B.

B's d~tor is invoked and processed before D's d~tor can complete.
I was thinking that this would be true only for non-virtual dtor case,
but I wouldn't have expected it happen for a virtual dtor.
For a class with a virtual dtor I would have expected that only
the dtor of D would be called when D gets deleted.

You are describing a non-virtual d~tor. Only D's d~tor would be invoke.
Which is the crux of the arguement of why a derived class needs the base to
have the virtual d~tor.
What's correct? Is maybe my compiler buggy?

class B
{
public:
B() {}
virtual ~B() { std::cout << "~B\n"; }
};

class D : public B
{
public:
D() {}
virtual ~D() { std::cout << "~D\n"; }
};

void virtual_dtor_tester()
{
B b;
D d;
}

/* output:
~D
~B
~B
*/

Try:

#include <iostream>

class B
{
public:
B() { std::cout << "B\n"; }
virtual ~B() { std::cout << "~B\n"; }
};

class D : public B
{
public:
D() { std::cout << "D\n"; }
~D() { std::cout << "~D\n"; }
};

class E : public D
{
public:
E() { std::cout << "E\n"; }
~E() { std::cout << "~E\n"; }
};


int main(int argc, char* argv[])
{
E e;

return 0;
}

/*
B
D
E
~E
~D
~B
*/

Note: d~tors ~D() and ~E() are automatically virtual.
 
O

Old Wolf

Peter said:
You are describing a non-virtual d~tor. Only D's d~tor would be
invoke. Which is the crux of the arguement of why a derived class
needs the base to have the virtual d~tor.

Please don't answer questions here if you don't know what
you are talking about. Invoking a derived class's destructor
ALWAYS calls the base class destructors.

The virtualness of the base class destructor only comes into
play when the object is deleted via a pointer to the base
class.
Try:

#include <iostream>

class B
{
public:
B() { std::cout << "B\n"; }
virtual ~B() { std::cout << "~B\n"; }
};

class D : public B
{
public:
D() { std::cout << "D\n"; }
~D() { std::cout << "~D\n"; }
};

class E : public D
{
public:
E() { std::cout << "E\n"; }
~E() { std::cout << "~E\n"; }
};


int main(int argc, char* argv[])
{
E e;

return 0;
}

/*
B
D
E
~E
~D
~B
*/

The output will be the same even if B's dtor is not virtual.
Try it and see.
 
J

John Carson

The code below illustrates the difference between the virtual (V) and
non-virtual (NV) cases. The difference occurs when you have a base pointer
to a derived class.


#include <iostream>

class BaseV
{
public:
BaseV() { std::cout << "BaseV\n"; }
virtual ~BaseV() { std::cout << "~BaseV\n"; }
};

class DerivedV : public BaseV
{
public:
DerivedV() { std::cout << "DerivedV\n"; }
~DerivedV() { std::cout << "~DerivedV\n"; }
};

class BaseNV
{
public:
BaseNV() { std::cout << "BaseNV\n"; }
~BaseNV() { std::cout << "~BaseNV\n"; }
};

class DerivedNV : public BaseNV
{
public:
DerivedNV() { std::cout << "DerivedNV\n"; }
~DerivedNV() { std::cout << "~DerivedNV\n"; }
};


int main(int argc, char* argv[])
{
BaseV *ptrV = new DerivedV;
delete ptrV;

BaseNV *ptrNV = new DerivedNV;
delete ptrNV;
}
 
T

tuvok

in message


Not really, the virtual d~tor of base class is invoked, not called.


B's d~tor is invoked and processed before D's d~tor can complete.


You are describing a non-virtual d~tor. Only D's d~tor would be invoke.

No, it's not the case. Both ~D and ~B are invoked no matter whether
the dtors are virtual or not. So there seems no difference whether
the dtors are virtual or not. Ie. always the dtors of all parts get invoked.
 
T

tuvok

The code below illustrates the difference between the virtual (V) and
non-virtual (NV) cases. The difference occurs when you have a base pointer
to a derived class.


#include <iostream>

class BaseV
{
public:
BaseV() { std::cout << "BaseV\n"; }
virtual ~BaseV() { std::cout << "~BaseV\n"; }
};

class DerivedV : public BaseV
{
public:
DerivedV() { std::cout << "DerivedV\n"; }
~DerivedV() { std::cout << "~DerivedV\n"; }
};

class BaseNV
{
public:
BaseNV() { std::cout << "BaseNV\n"; }
~BaseNV() { std::cout << "~BaseNV\n"; }
};

class DerivedNV : public BaseNV
{
public:
DerivedNV() { std::cout << "DerivedNV\n"; }
~DerivedNV() { std::cout << "~DerivedNV\n"; }
};


int main(int argc, char* argv[])
{
BaseV *ptrV = new DerivedV;
delete ptrV;

BaseNV *ptrNV = new DerivedNV;
delete ptrNV;
}

I see. Thanks.
I've slightly extended it to show the difference also vs. stack objects:

void test_virt_vs_nonvirt_dtor()
{
std::cout << "Derived objects created on stack:\n";
{
std::cout << " Creating:\n";
DerivedV dv;
DerivedNV dnv;
std::cout << " Destroying:\n";
}

std::cout << "\nDerived objects using ptr to base created on heap:\n";
std::cout << " Creating:\n";
BaseV *ptrV = new DerivedV;
BaseNV *ptrNV = new DerivedNV;
std::cout << " Destroying:\n";
delete ptrNV;
delete ptrV;
}

Here's the output:

Derived objects created on stack:
Creating:
BaseV
DerivedV
BaseNV
DerivedNV
Destroying:
~DerivedNV
~BaseNV
~DerivedV
~BaseV

Derived objects using ptr to base created on heap:
Creating:
BaseV
DerivedV
BaseNV
DerivedNV
Destroying:
~BaseNV
~DerivedV
~BaseV

So, bottom line is: using ptr to base does not (can not) call the dtor of derived,
unless the dtor is virtual.
 
P

Peter Julian

Old Wolf said:
Please don't answer questions here if you don't know what
you are talking about. Invoking a derived class's destructor
ALWAYS calls the base class destructors.

He said "when D gets deleted". Please don't criticize my comments if you
can't even read the post. Incidentally, the base destructor isn't called,
its invoked. That means that you can't overide the base d~tor, in case you
are wondering what the difference is.

In the case a pointer to D is was involved, B's d~tor would not be invoked.

While my example didn't include a pointer to the derived class, the output
doesn't produce the warning.
 
P

Peter Julian

tuvok said:
No, it's not the case. Both ~D and ~B are invoked no matter whether
the dtors are virtual or not. So there seems no difference whether
the dtors are virtual or not. Ie. always the dtors of all parts get invoked.

In the case where a pointer isn't involved, the base's d~tor will still be
invoked but you'll have the warning. You said "when D gets deleted". In the
case you had a pointer, something i didn't include in my example, your base
c~tor would not have been invoked.
 
H

Howard

Peter Julian said:
In the case where a pointer isn't involved, the base's d~tor will still be
invoked but you'll have the warning. You said "when D gets deleted". In
the
case you had a pointer, something i didn't include in my example, your
base
c~tor would not have been invoked.

Eh? Of course the base class destructor is invoked! Whenever a derived
class is destroyed, the destructors for any base classes *must* be invoked,
whether you're using pointers or automatic variables. Otherwise, the memory
allocated for the base class portion(s) of the object would never get
returned to the system.

This is even true if the derived class object was assigned to a base class
pointer variable. The only difference in that case is that the base class
destructor needs to be virtual, or else the *derived* class destructor will
not get invoked.

There is no way for the destruction of the derived class to *not* also
invoke the destructor of the base class (aside from invoking undefined
behavior or abnormally terminating the process).

-Howard
 
H

Howard

"c~tor"???

You *did* mean "destructor" there, didn't you? I saw the ~, and missed the
"c" in front, but you were talking about destructors, so I'm assuming that's
what you meant. If you mean the "constructor", though, my argument is the
same...the base classes' constructor(s) must also be invoked, either
explicitly or implicitly, whenever a derived class it created, regardless of
whether you're using pointers or automatic variables.

-Howard
 
P

Peter Koch Larsen

Peter Julian said:
He said "when D gets deleted". Please don't criticize my comments if you
can't even read the post. Incidentally, the base destructor isn't called,
its invoked. That means that you can't overide the base d~tor, in case you
are wondering what the difference is.

In the case a pointer to D is was involved, B's d~tor would not be
invoked.

This is plain rubbish. If D inherits from B, B's destructor will be invoked
when deleting a D-object. And this has nothing to do with whether a
destructor is virtual or not.

/Peter
 
R

Ron Natalie

tuvok said:
Is it correct that the virtual dtor of base gets called implicitly?

Yes. Let me reiterate to make sure you understand.

When an object is created, at the level of the most
derived object all the virtual bases are constructed
(in a depth first, left to right fashion). Then the
direct bases of the classes are constructed (in the
order listed in the class definition, not the mem-initializer
list), and then each non-static data member is constructed.
Then the constructor body of the object is run.

Each subobject above recursively constructs it's subobjects
(with the exception of avoiding duplicate construction of
the virtual bases).

There is nothing the program can do to change that ordering
(within defined behavior).


Objects are destructed in precisely the REVERSE ORDERING.

Virtualness of the destructor, arguments to the constructor,
etc... have no bearing on this behavior. It always happens
in the same topological order described above.
 
P

Peter Julian

Peter Koch Larsen said:
This is plain rubbish. If D inherits from B, B's destructor will be invoked
when deleting a D-object. And this has nothing to do with whether a
destructor is virtual or not.

That was a typo or rather a mistake. I meant a base B pointer to D. As in...

int main(int argc, char* argv[])
{
B* p_d = new D;

delete p_d;

return 0;
}
 
P

Peter Julian

Howard said:
Eh? Of course the base class destructor is invoked! Whenever a derived
class is destroyed, the destructors for any base classes *must* be invoked,
whether you're using pointers or automatic variables. Otherwise, the memory
allocated for the base class portion(s) of the object would never get
returned to the system.

This is even true if the derived class object was assigned to a base class
pointer variable. The only difference in that case is that the base class
destructor needs to be virtual, or else the *derived* class destructor will
not get invoked.

There is no way for the destruction of the derived class to *not* also
invoke the destructor of the base class (aside from invoking undefined
behavior or abnormally terminating the process).

-Howard

I meant a Base * to a new D. My mistake. Its been a long day.
 
J

Jim Langston

Ron Natalie said:
Yes. Let me reiterate to make sure you understand.

When an object is created, at the level of the most
derived object all the virtual bases are constructed
(in a depth first, left to right fashion). Then the
direct bases of the classes are constructed (in the
order listed in the class definition, not the mem-initializer
list), and then each non-static data member is constructed.
Then the constructor body of the object is run.

Each subobject above recursively constructs it's subobjects
(with the exception of avoiding duplicate construction of
the virtual bases).

There is nothing the program can do to change that ordering
(within defined behavior).


Objects are destructed in precisely the REVERSE ORDERING.

Virtualness of the destructor, arguments to the constructor,
etc... have no bearing on this behavior. It always happens
in the same topological order described above.

I just wrote a program and compiled and ran to test this theory, and it
seems
that "Objects are destructed in precisely the REVERSE ORDERING" is
not always true. Here is the program:

#include <iostream>

class base
{
public:
int i;
base() { std::cout << "b ctor" << std::endl; }
~base() { std::cout << "b dtor" << std::endl; }
};

class derived : public base
{
public:
derived() { std::cout << "d ctor" << std::endl; }
~derived() { std::cout << "d dtor" << std::endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
base* b;
derived* d;

b = new derived;
delete b;

std::cout << std::endl;

d = new derived;
delete d;

char c;
std::cin >> c;

return 0;

}

And it's Output:

b ctor
d ctor
b dtor

b ctor
d ctor
d dtor
b dtor

As you can see, the derived dtor was NOT called when the derived was
instatized to a base pointer.

Change the base dtor to:

produces this output:
b ctor
d ctor
d dtor
b dtor

b ctor
d ctor
d dtor
b dtor

Where the derived dtor is called in both instances.

But, yes, the base dtor does get called in either case either.
 
R

Ron Natalie

Jim said:
I just wrote a program and compiled and ran to test this theory, and it
seems
that "Objects are destructed in precisely the REVERSE ORDERING" is
not always true. Here is the program:
I was referring to the order of destruction of subobjects in
an object.
 
R

Ron Natalie

Jim said:
int _tmain(int argc, _TCHAR* argv[])
{
base* b;
derived* d;

b = new derived;
delete b;
This is UNDEFINED BEHAVIOR... your program is incorrect
and there is no point in even conjecture as to what
the observable behavior is.

It has no bearing on the original problem or my answer.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,898
Latest member
BlairH7607

Latest Threads

Top