Virtual Destructor

S

Stub

Please answer my questions below - thanks!

1. Why "Derived constructor" is called but "Derived destructor" not in Case
1 since object B is new'ed from Derived class?
2. Why "Derived destructor" is called in Case 2 since only ~base() becomes
"virtual" and ~Derived() is still non-virtual?
3. Does Case 3 show that we don't need any virtual destructor to make
~Derived() called?
4. Is "virtual destructor" needed only for Case 2?


Case 1:
========

class base
{
public:
~base() { cout << "Base destructor\n"; }
};


class Derived : public base
{
public:
Derived() { cout << "Derived constructor\n"; }
~Derived() { cout << "Derived destructor\n"; }
};


int main(){
base* B = new Derived;
delete B;
return 0;
}

OUTPUT:
Derived constructor
Base destructor



Case 2:
========

class base
{
public:
virtual ~base() { cout << "Base destructor\n"; }
};


class Derived : public base
{
public:
Derived() { cout << "Derived constructor\n"; }
~Derived() { cout << "Derived destructor\n" }
};


int main () {

base* B = new Derived;
delete B;
return 0;
}

OUTPUT:
Derived constructor
Derived destructor
Base destructor



Case 3:
=========

class base
{
public:
~base() { cout << "Base destructor\n"; }
};


class Derived : public base
{
public:
Derived() { cout << "Derived constructor\n"; }
~Derived() { cout << "Derived destructor\n" }
};


int main () {

Derived* B = new Derived;
delete B;
return 0;
}

OUTPUT:
Derived constructor
Derived destructor
Base destructor
 
V

Victor Bazarov

Stub said:
Please answer my questions below - thanks!

1. Why "Derived constructor" is called but "Derived destructor" not in Case
1 since object B is new'ed from Derived class?

Probably because there is no virtual destructor and you're deleting
the object through a pointer to class 'base', not 'Derived'. HOWEVER,
this behaviour is simply a chance occurrence because if destructor is
not virtual an attempt to delete an object of a derived type through
a pointer to a base class causes _undefined_behaviour_.
2. Why "Derived destructor" is called in Case 2 since only ~base() becomes
"virtual" and ~Derived() is still non-virtual?

The "since" part of the question is false. If the base class' d-tor
is virtual, all derived class' d-tors are virtual too.
3. Does Case 3 show that we don't need any virtual destructor to make
~Derived() called?

I suppose it does. If you delete it through the pointer to Derived,
there is no need in a virtual destructor.
4. Is "virtual destructor" needed only for Case 2?

No. Case 1 requires a virtual destructor. Without it the behaviour
of the Case 1 program is _undefined_.
 
S

Stuart Golodetz

Stub said:
Please answer my questions below - thanks!

I would, but this does look suspiciously like homework to me. We're all more
than happy to help with that if there's some evidence of an effort having
been made, but no-one here is going to do your homework for you. Besides,
I've got more than enough of my own... :) FWIW, I'll explain the underlying
idea to you, and if you can figure out the answers to the questions from
that, then so much the better:

Given a base class B and a class D (publicly) derived from B, the following
scenario exhibits undefined behaviour iff B's destructor is non-virtual:

B *p = new D;
delete p;

(Interesting aside - The following always exhibits undefined behaviour,
whether or not B has a virtual destructor:
B *p = new D[5];
delete [] p;
)

This is essentially because the compiler merely invokes the destructor
associated with the static type of p (i.e. B's destructor) as it doesn't
realise it's supposed to invoke D's destructor. Much as:

class B
{
public:
void f() { std::cout << "B"; }
};

class D : public B
{
public:
void f() { std::cout << "D"; }
};

B *p = new D;
p->f();
....

will output B, since f is non-virtual. However, if we did:

D *q = new D;
q->f();
....

then it would output D, since q is a D *.

Hope that helped a bit, I'll let you figure out the rest for yourself.

Cheers,

Stuart.
 
S

Stub

Victor Bazarov said:
Probably because there is no virtual destructor and you're deleting
the object through a pointer to class 'base', not 'Derived'. HOWEVER,
this behaviour is simply a chance occurrence because if destructor is
not virtual an attempt to delete an object of a derived type through
a pointer to a base class causes _undefined_behaviour_.


The "since" part of the question is false. If the base class' d-tor
is virtual, all derived class' d-tors are virtual too.
This is the part which confused me...

First, is Case 2 the right way to implement virtual destructor so that when
deleting an object of Derived type via its base class pointer, the
destructor in Derived will be called for sure?

Second, the virtual d-tor, "~base()", in base has a different function name
compared to the virtual d-tor in Derived, "~Derived()." How could it make
"~Derived()" automatically virtual here? Is there any overriding here given
their names different? Or is this just designed by C++ Standard. See, I am
just trying to understand the concept here.

Thanks for your help!
 
V

Victor Bazarov

Stub said:
This is the part which confused me...

First, is Case 2 the right way to implement virtual destructor so that when
deleting an object of Derived type via its base class pointer, the
destructor in Derived will be called for sure?

Case 2 is fine.
Second, the virtual d-tor, "~base()", in base has a different function name
compared to the virtual d-tor in Derived, "~Derived()."

It doesn't matter. It's a special function. Names here are not important.
How could it make
"~Derived()" automatically virtual here?

What's the difference _how_ it does that? The Standard requires it, so it
has to figure a way.
Is there any overriding here given
their names different? Or is this just designed by C++ Standard. See, I am
just trying to understand the concept here.

The concept is this: for any class T if any of its base classes have their
destructor declared "virtual", the T's destructor is virtual, no ifs, ands
or buts.

Get a copy of the Standard, it's only $18 (in a PDF form). I strongly
recommend it.

Victor
 
D

Dan Cernat

Stuart Golodetz said:
[snip]


(Interesting aside - The following always exhibits undefined behaviour,
whether or not B has a virtual destructor:
B *p = new D[5];
delete [] p;
)

Stuart, you know better!. If the destructor is virtual, the code is OK.

See the code below and the output at the end

#include <iostream>

using namespace std;

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

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


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

delete[] p;

return 0;
}


D destructor
B destructor
D destructor
B destructor
D destructor
B destructor
D destructor
B destructor
D destructor
B destructor

which is the right thing. (VC++ 6.0)

/dan
 
M

Max M

Dan said:
(Interesting aside - The following always exhibits undefined behaviour,
whether or not B has a virtual destructor:
B *p = new D[5];
delete [] p;
)

Stuart, you know better!. If the destructor is virtual, the code is OK.

No, it's not.
See the code below and the output at the end

Add a data member to D, and see what happens.

Max
 
G

Gary Labowitz

Max M said:
Dan said:
(Interesting aside - The following always exhibits undefined behaviour,
whether or not B has a virtual destructor:
B *p = new D[5];
delete [] p;
)

Stuart, you know better!. If the destructor is virtual, the code is OK.

No, it's not.
See the code below and the output at the end

Add a data member to D, and see what happens.

Interesting. Why is that?
 
R

Rob Williscroft

Max M wrote in
Dan said:
(Interesting aside - The following always exhibits undefined
behaviour, whether or not B has a virtual destructor:
B *p = new D[5];
delete [] p;
)

Stuart, you know better!. If the destructor is virtual, the code is
OK.

No, it's not.
See the code below and the output at the end

Add a data member to D, and see what happens.

Just for fun I did. I tried vc7.1 first and it worked just fine,
gcc 3.2 (MingW) seg' faulted and bcc called the B destructor 5 times
but no seg' fault (It probably would have if B's dtor had been more
complex, i.e. derefrenced this pointer in some significant way).

Clearly vc7.1 stores sizeof info in the poloymorfic type (or in its
vtable) and does "the right thing" when deleteing a polymorphic array.

AFAICT vc7.1's behaviour an extension to the standard, I really can't
imagine why they did it :).

FWIW here's the code:

#include <iostream>

using std::cerr;

int ctor = 0;

struct test
{
int data[10];

test()
{
for ( int i = 0; i < 10; ++i )
data = 0x5AA55AA5;

data[1] = ++ctor;
}
~test()
{
cerr << "~test(" << data[1] << ")\n";
}
};

class B
{
public:
virtual ~B()
{
poly();
cerr << "B destructor\n";
}
virtual void poly() { cerr << "poly B\n"; }
};

class D : public B
{
test data_member;
public:
~D()
{
poly();
cerr << "D destructor\n";
}
virtual void poly() { cerr << "poly D\n"; }
};


int main()
{
B *p = new D[5];

delete[] p;
}

Rob.
 
D

Dan Cernat

Max M said:
Dan said:
(Interesting aside - The following always exhibits undefined behaviour,
whether or not B has a virtual destructor:
B *p = new D[5];
delete [] p;
)

Stuart, you know better!. If the destructor is virtual, the code is OK.

No, it's not.
See the code below and the output at the end

Add a data member to D, and see what happens.

Max

I tried your suggestion and although I got the behaviour one would
expect (destruct of derived objects) I have to agree that it could
lead to undefined behaviour, especially if the pointer is passed
around and one tries to access its members (not at the index 0).
However, the memory manager seems that is doing the right job.

the new code:

#include <iostream>

using namespace std;

class InDerived
{
public:
~InDerived() {cout << "Destructor InDerived\n";}
};

class InBase
{
public:
~InBase() {cout << "Destructor InBase\n";}
};


class base
{
public:
InBase m_inBase;
virtual ~base() {cout << "Destructor base\n";}
};

class derived : public base
{
public:
InDerived m_inDerived;
int x;
~derived() {cout << "Destructor derived\n";}
};

void DeleteArray(base *p)
{
delete [] p;
}

int _tmain(int argc, _TCHAR* argv[])
{
base *p = new derived[5];

DeleteArray(p);


return 0;
}
 
R

Ron Natalie

Dan Cernat said:
I tried your suggestion and although I got the behaviour one would
expect (destruct of derived objects) I have to agree that it could
lead to undefined behaviour, especially if the pointer is passed
around and one tries to access its members (not at the index 0).
However, the memory manager seems that is doing the right job.

Not lead to undefined behavior, it is undefined behavior. Coincidentally
working is one of those insidious undefined behaviors. Try putting
some more data members in. It will be sure to break. The issue is not
just delete, you can ONLY convert the derived pointer of one object to
a base class. It doesn't work in arrays.

Image sizeof(base) is 4. sizeof(derived) is 8.
What is the relationship the base_array[1] with respect to base_array[0].
What is the relationship of derived_array[1] to derived_array[0].
Just think about it for a minute.
 
D

Dan Cernat

Ron Natalie said:
I tried your suggestion and although I got the behaviour one would
expect (destruct of derived objects) I have to agree that it could
lead to undefined behaviour, especially if the pointer is passed
around and one tries to access its members (not at the index 0).
However, the memory manager seems that is doing the right job.

Not lead to undefined behavior, it is undefined behavior. Coincidentally
working is one of those insidious undefined behaviors. Try putting
some more data members in. It will be sure to break. The issue is not
just delete, you can ONLY convert the derived pointer of one object to
a base class. It doesn't work in arrays.

Image sizeof(base) is 4. sizeof(derived) is 8.
What is the relationship the base_array[1] with respect to base_array[0].
What is the relationship of derived_array[1] to derived_array[0].
Just think about it for a minute.
Yup, this is what I wanted to say in my prevoius message and I totally agree
with what you say.
I really should think twice before posting. Point taken.

Thanks,
Dan
 

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,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top