Virtual Destructor

  • Thread starter Prawit Chaivong
  • Start date
P

Prawit Chaivong

Hi All,

There is code here.
------------------------------------------------------------------
class Base{
public:
Base(){}
virtual ~Base(){}
private:
int a;
};

class Derived : public Base{
public:
Derived(){}
virtual ~Derived(){}
private:
int b;
};

int main()
{
Base* pb = new Derived[10];
delete[] pb; // <-- crash HERE.
return 0;
}
-------------------------------------------------------------------
From code, I know that I shouldn't delete Derived object by its parent
pointer. Because size of both are not similar.

But my question is when I remove keyword 'virtual' out of
destructor(both classes). This program is not crash anymore.
I just want to know why it's not crash if its dtor is not virtual.

By the way, I use gcc 3.2.3.

Thank in advance.
Prawit Chaivong
 
A

Alf P. Steinbach

* Prawit Chaivong:
class Base{
public:
Base(){}
virtual ~Base(){}
private:
int a;
};

class Derived : public Base{
public:
Derived(){}
virtual ~Derived(){}
private:
int b;
};

int main()
{
Base* pb = new Derived[10];
delete[] pb; // <-- crash HERE.
return 0;
}
-------------------------------------------------------------------
From code, I know that I shouldn't delete Derived object by its parent
pointer. Because size of both are not similar.

But my question is when I remove keyword 'virtual' out of
destructor(both classes). This program is not crash anymore.
I just want to know why it's not crash if its dtor is not virtual.

The program is incorrect with or without a virtual destructor.

You have run up against a flaw in the type system, inherited from C,
namely that an array is represented by a pointer to its first element.

To 'delete' an array you must use a pointer of the exact type the
array was allocated with. The compiler infers the memory size of each
array element from the pointer type. Using the wrong pointer type you
essentially lie to the compiler about the element size, so even though
your program doesn't crash with non-virtual destructor that's just
accidental: it could just as well crash, or do anything; it's UB.

Here's another example of this same type system flaw (off the cuff):

#include <iostream>
#include <ostream>

struct A{ virtual int id() const { return 1; } };
struct B: A { char dummy; virtual int id() const { return 2; } };

void display( A const* objects, int n )
{
for( int i = 0; i < n; ++i )
{
std::cout << objects.id() << std::endl;
}
}

int main()
{
B const array[3] = {};
display( array, 3 );
}

To get polymorphism in an array, store pointers to the objects.
 
P

Prawit Chaivong

Yes, I know that this program is incorrect.
My POINT is I just want to know that why when I use virtual, this
program suddently crash, whilst non-virtual doesn't.
 
J

Jack Klein

Yes, I know that this program is incorrect.
My POINT is I just want to know that why when I use virtual, this
program suddently crash, whilst non-virtual doesn't.

You break the rules, you cause undefined behavior. The C++ standard
does not specify what happens when you cause undefined behavior. It
could crash, it could seem to work right, it could format your hard
disk. C++ does not say, does not know, does not care.
 
P

Prawit Chaivong

That's ok if you guys don't want to go deep a little bit further. I'll
do it alone.
Because I want to know what's going on under the surface.
 
L

Lionel B

Prawit said:
That's ok if you guys don't want to go deep a little bit further. I'll
do it alone.
Because I want to know what's going on under the surface.

Alf P. Steinbach answered your question very precisely: the upshot is that your code (whether the dtor is virtual or
not) is incorrect and invokes undefined behaviour. If you want to know how your particular compiler handles this
particular case of undefined behaviour, then... well, why would you want to know that? There's nothing very interesting
under that surface and hence no-one in this newsgroup is likely to be interested in discussing it.
 
B

baumann@pan

Alf said:
* Prawit Chaivong:
class Base{
public:
Base(){}
virtual ~Base(){}
private:
int a;
};

class Derived : public Base{
public:
Derived(){}
virtual ~Derived(){}
private:
int b;
};

int main()
{
Base* pb = new Derived[10];
delete[] pb; // <-- crash HERE.
return 0;
}
-------------------------------------------------------------------
From code, I know that I shouldn't delete Derived object by its parent
pointer. Because size of both are not similar.

But my question is when I remove keyword 'virtual' out of
destructor(both classes). This program is not crash anymore.
I just want to know why it's not crash if its dtor is not virtual.

The program is incorrect with or without a virtual destructor.

You have run up against a flaw in the type system, inherited from C,
namely that an array is represented by a pointer to its first element.

To 'delete' an array you must use a pointer of the exact type the
array was allocated with. The compiler infers the memory size of each
array element from the pointer type. Using the wrong pointer type you
essentially lie to the compiler about the element size, so even though
your program doesn't crash with non-virtual destructor that's just
accidental: it could just as well crash, or do anything; it's UB.

Here's another example of this same type system flaw (off the cuff):

#include <iostream>
#include <ostream>

struct A{ virtual int id() const { return 1; } };
struct B: A { char dummy; virtual int id() const { return 2; } };

void display( A const* objects, int n )
{
for( int i = 0; i < n; ++i )
{
std::cout << objects.id() << std::endl;
}
}

int main()
{
B const array[3] = {};
display( array, 3 );
}

To get polymorphism in an array, store pointers to the objects.


how?
thanks much!!!!
 
B

baumann@pan

Alf said:
* Prawit Chaivong:
class Base{
public:
Base(){}
virtual ~Base(){}
private:
int a;
};

class Derived : public Base{
public:
Derived(){}
virtual ~Derived(){}
private:
int b;
};

int main()
{
Base* pb = new Derived[10];
delete[] pb; // <-- crash HERE.
return 0;
}
-------------------------------------------------------------------
From code, I know that I shouldn't delete Derived object by its parent
pointer. Because size of both are not similar.

But my question is when I remove keyword 'virtual' out of
destructor(both classes). This program is not crash anymore.
I just want to know why it's not crash if its dtor is not virtual.

The program is incorrect with or without a virtual destructor.

You have run up against a flaw in the type system, inherited from C,
namely that an array is represented by a pointer to its first element.

To 'delete' an array you must use a pointer of the exact type the
array was allocated with. The compiler infers the memory size of each
array element from the pointer type. Using the wrong pointer type you
essentially lie to the compiler about the element size, so even though
your program doesn't crash with non-virtual destructor that's just
accidental: it could just as well crash, or do anything; it's UB.

Here's another example of this same type system flaw (off the cuff):

#include <iostream>
#include <ostream>

struct A{ virtual int id() const { return 1; } };
struct B: A { char dummy; virtual int id() const { return 2; } };

void display( A const* objects, int n )
{
for( int i = 0; i < n; ++i )
{
std::cout << objects.id() << std::endl;
}
}

int main()
{
B const array[3] = {};
display( array, 3 );
}

To get polymorphism in an array, store pointers to the objects.



how To get
polymorphism in an array?

thanks much.
 
B

baumann@pan

Alf said:
* Prawit Chaivong:
class Base{
public:
Base(){}
virtual ~Base(){}
private:
int a;
};

class Derived : public Base{
public:
Derived(){}
virtual ~Derived(){}
private:
int b;
};

int main()
{
Base* pb = new Derived[10];
delete[] pb; // <-- crash HERE.
return 0;
}
-------------------------------------------------------------------
From code, I know that I shouldn't delete Derived object by its parent
pointer. Because size of both are not similar.

But my question is when I remove keyword 'virtual' out of
destructor(both classes). This program is not crash anymore.
I just want to know why it's not crash if its dtor is not virtual.

The program is incorrect with or without a virtual destructor.

You have run up against a flaw in the type system, inherited from C,
namely that an array is represented by a pointer to its first element.

To 'delete' an array you must use a pointer of the exact type the
array was allocated with. The compiler infers the memory size of each
array element from the pointer type. Using the wrong pointer type you
essentially lie to the compiler about the element size, so even though
your program doesn't crash with non-virtual destructor that's just
accidental: it could just as well crash, or do anything; it's UB.

Here's another example of this same type system flaw (off the cuff):

#include <iostream>
#include <ostream>

struct A{ virtual int id() const { return 1; } };
struct B: A { char dummy; virtual int id() const { return 2; } };
if display function refer objects as an array, how to get
polymorphism??

thanks
much!!!!!!!!!!!!!!!!!!!!!

baumann@pan
void display( A const* objects, int n )
{
for( int i = 0; i < n; ++i )
{
std::cout << objects.id() << std::endl;
}
}

int main()
{
B const array[3] = {};
display( array, 3 );
}

To get polymorphism in an array, store pointers to the objects.

--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
 
A

Alf P. Steinbach

* baumann@pan:
if display function refer objects as an array, how to get
polymorphism??

Uhm, first, it's not necessary to post essentially the same
question three times. ;-)

Off the cuff (that means untested, never touched by dirty
compiler's hands), here's some code:

#include <iostream> // std::cout
#include <ostream> // <<, std::endl
#include <memory> // std::auto_ptr
#include <vector> // std::vector

struct A { virtual int id() const { return 1; } };
struct B: A { virtual int id() const { return 2; } }

// Encapsulate array in a class for safe deallocation.
class ArrayOfA
{
private:
std::vector<A*> myItems;
public:
A(): myRawArray( 0 ) {}

~ArrayOfA()
{
for( std::size_t i = 0; i < myItems.size(); ++i )
{
delete myItems.at( i );
}
}

A& at( int i ) { return myItems.at( i ); }
A const& at( int i ) const { return myItems.at( i ); }
int i size() const{ return static_cast<int>( myItems.size() ); }

// Add operator[] here if you want that; I don't.
// Using 'at' is much safer than the []-notation.

void add( std::auto_ptr<A> anItem )
{
myItems.push_back( anItem.get() );
}
}

void display( ArrayOfA const& items )
{
for( int i = 0; i < items.size(); ++i )
{
std::cout << items.at( i ).id() << std::endl;
}
}

ArrayOfA hodgePodgeArray()
{
ArrayOfA array;

array.add( new A );
array.add( new B );
array.add( new A );
array.add( new B );
array.add( new B );
return array;
}

int main()
{
display( hodgePodgeArray() );
}

This code has a theoretically big problem: it doesn't transfer ownership
of the pointers when you assign or copy construct ArrayOfA, so if you're
not careful you might end up deleting the same object twice, which is UB.

To address that issue you might want to make the vector elements
boost::shared_ptr<A> (there are also other more DIY solutions);
unfortunately std::auto_ptr<A> can't be used since it's not copyable.

Check out the Boost library at <url: http://www.boost.org>.
 
A

Alf P. Steinbach

* Max M.:
You meant 'anItem.release()' here, didn't you?

Yes and no.

I did mean 'get', but I forgot to add a 'release' call after
the 'push_back'.

I.e., if the 'push_back' succeeds, then release the darned
pointer, where the 'if' is taken care of by exception or not.

Thanks,

- Alf
 
P

Peter Koch Larsen

Prawit Chaivong said:
That's ok if you guys don't want to go deep a little bit further. I'll
do it alone.
Because I want to know what's going on under the surface.
The answer is that for classes with virtual functions, your compiler creates
a hidden member in each object it creates - the vtable. This table contains
(presumably among other things) an array of function pointers. The vtable is
located somewhere in the object, for example in the beginning of the object.
Now when the array is deleted, something like this happens:

for (i = 0; i < 10; ++i)
{
ptr_to_destructor _function = pb.__hidden_vtable[0];
call ptr_to_destructor _function(pb + 1);
}

Now, for the second element the function looked up will contain garbage, and
so the call to the destructor will be at some random adress, causing the
crash.

/Peter
 
F

Fraser Ross

The OP wanted polymorphism in an array which you said can be done, then you
gave an example of doing it in a vector. Although it can be done with an
array it is tedious to do the setting up of the array. The objects would
have to be created and destroyed elsewhere.

Fraser.
 
A

Alf P. Steinbach

* Fraser Ross:
The OP wanted polymorphism in an array which you said can be done, then you
gave an example of doing it in a vector.

What do you think a vector is?

Although it can be done with an [raw] array it is tedious to do
the setting up of the array.

Nope, but some other things are tedious, hence the std::vector,
which takes care of all that tedious stuff, and does it using
code that's almost guaranteed to be correct.

Always use a std::vector (or e.g. Boost arrays) instead of raw
arrays.

If you can.

The objects would have to be created and destroyed elsewhere.

That statement doesn't make sense to me, i.e., you've probably
misunderstood something in addition to the misunderstandings above.

Cheers,

- Alf
 
K

Karl Heinz Buchegger

Fraser said:
The OP wanted polymorphism in an array which you said can be done, then you
gave an example of doing it in a vector. Although it can be done with an
array it is tedious to do the setting up of the array. The objects would
have to be created and destroyed elsewhere.

And that differs to the std::vector solution exactly how?
The important point is: If you want polymorphsm your data structure
needs to hold pointers. If that data structure is an array or if it
is a std::vector makes no difference.
 
F

Fraser Ross

"Karl Heinz Buchegger"
And that differs to the std::vector solution exactly how?
The important point is: If you want polymorphsm your data structure
needs to hold pointers. If that data structure is an array or if it
is a std::vector makes no difference.

I see now std::vector has the same problem with deallocation as an array.
Alfs program has UB. auto_ptr/shared_ptr won't work either. I don't know
the boost array.

I was thinking about an array set up like this:

class B {}
class D :B {}

B b[2];
D d[3];

B * array[5];
array[0]=&d[0];
array[1]=&b[1];
array[2]=&d[2];
array[3]=&b[0];
array[4]=&d[1];

The destruction of the elements pointed to is independent of the array.

Fraser.
 
F

Fraser Ross

"Fraser Ross"
I see now std::vector has the same problem with deallocation as an array.
Alfs program has UB. auto_ptr/shared_ptr won't work either. I don't know
the boost array.

No thats a mistake. The elements destructors are doing the deallocation ok.

Fraser.
 
R

Ron Natalie

Prawit said:
Yes, I know that this program is incorrect.
My POINT is I just want to know that why when I use virtual, this
program suddently crash, whilst non-virtual doesn't.
It's silly to try to distinguish why you get two different
behaviors when both cases are undefined behavior, but if we
want to play that mental game:

The case without the virtual destructor invokes ~Base.
The case with the virtual destructor attempts to invoke
the derived class destructor on each item, HOWEVER, the
size of the object is screwed up because ((Base*)bp)[1]
is a different address than ((Derived*)bp)[1]
 
A

Alf P. Steinbach

* Fraser Ross:
"Fraser Ross"

No thats a mistake. The elements destructors are doing the deallocation ok.

Note that I forgot to type '*' in the accessors. Bunger. But anyone
trying to compile the thing would see that.
 

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,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top