Proposed work-around for missing virtual destructors

  • Thread starter christopher diggins
  • Start date
C

christopher diggins

I posted to my blog a special pointer class work-around for inheriting from
base classes without virtual destructors. I was wondering if there is any
other similar work, and whether there are any problems with my proposed
approach. Thanks in advance!
See http://www.artima.com/weblogs/viewpost.jsp?thread=107587

For those who just want the code:

template<typename target_type>
class base_class_ptr {
public:
// forward declarations
template <class T>
struct functions;
template <class T>
base_class_ptr(T* x)
: m_a(x), m_t(&functions<T>::table)
{}
target_type* operator->() {
return static_cast<target_type*>(m_a);
}
void Delete() {
m_t->Delete(m_a);
m_a = NULL;
}
// Function table type
struct table {
void (*Delete)(void*);
};
// For a given referenced type T, generates functions for the
// function table and a static instance of the table.
template<class T>
struct functions
{
static typename base_class_ptr<target_type>::table table;
static void Delete(void* p) {
delete static_cast<T*>(p);
}
};
private:
void* m_a;
table* m_t;
};

template<typename target_T>
template<class T>
typename base_class_ptr<target_T>::table
base_class_ptr<target_T>::functions<T>::table = {
&base_class_ptr<target_T>::template functions<T>::Delete
};
 
P

Pete Becker

christopher said:
I posted to my blog a special pointer class work-around for inheriting from
base classes without virtual destructors. I was wondering if there is any
other similar work, and whether there are any problems with my proposed
approach.

Too many void*'s. The result is that this isn't typesafe:

struct T {};
struct U {};

base_class_ptr<T>( (U*)0 );

The code doesn't enforce the implied requirement that U is derived from
T. That can be fixed by storing the pointer as a T* instead of a void*,
as is (typically) done in TR1's shared_ptr.

I'd also give this thread a different title: "Evading Design Decisions:
Deleting Types Derived from Classes Without Virtual Destructors". After
all, there's usually a good reason for not providing a virtual destructor.
 
A

Axter

christopher said:
I posted to my blog a special pointer class work-around for inheriting from
base classes without virtual destructors. I was wondering if there is any
other similar work, and whether there are any problems with my proposed
approach. Thanks in advance!
See http://www.artima.com/weblogs/viewpost.jsp?thread=107587

For those who just want the code:

template<typename target_type>
class base_class_ptr {
public:
// forward declarations
template <class T>
struct functions;
template <class T>
base_class_ptr(T* x)
: m_a(x), m_t(&functions<T>::table)
{}
target_type* operator->() {
return static_cast<target_type*>(m_a);
}
void Delete() {
m_t->Delete(m_a);
m_a = NULL;
}
// Function table type
struct table {
void (*Delete)(void*);
};
// For a given referenced type T, generates functions for the
// function table and a static instance of the table.
template<class T>
struct functions
{
static typename base_class_ptr<target_type>::table table;
static void Delete(void* p) {
delete static_cast<T*>(p);
}
};
private:
void* m_a;
table* m_t;
};

template<typename target_T>
template<class T>
typename base_class_ptr<target_T>::table
base_class_ptr<target_T>::functions<T>::table = {
&base_class_ptr<target_T>::template functions<T>::Delete
};


I would avoid using void.
The following class is a safer workaround method, and it's type safe.

template<typename base_type>
class base_class_ptr
{
class BaseContainer{
public:
virtual ~BaseContainer(){}
};
template<typename derive_type>
class Container : public BaseContainer{
derive_type *m_derive_ptr;
public:
Container(derive_type* derive_obj):m_derive_ptr(derive_obj){}
~Container(){delete m_derive_ptr;}
};
public:
template<typename derive_type>
base_class_ptr(derive_type* derive_obj)
:m_base_ptr(derive_obj), m_BaseContainer(new
Container<derive_type>(derive_obj)){}
~base_class_ptr(){delete m_BaseContainer;}
base_type* operator->() {return m_base_ptr;}
private:
base_type *m_base_ptr;
BaseContainer *m_BaseContainer;
};

Example usage:
void SomeFunction()
{
base_class_ptr<MyBaseClass> ptr_a(new MyDerivedClass_a);
ptr_a->TestFunct();
base_class_ptr<MyBaseClass> ptr_b(new MyDerivedClass_b);
ptr_b->TestFunct();
}
 
A

Abecedarian

christopher said:
It is commonly recommended in C++ to publicly inherit from
classes which have virtual destructors, to avoid possible memory
leaks.

Not memory leaks but memory corruption. Undefined behavior in general.
I posted to my blog a special pointer class work-around for inheriting from
base classes without virtual destructors. I was wondering if there is any
other similar work, and whether there are any problems with my proposed
approach. Thanks in advance!
See http://www.artima.com/weblogs/viewpost.jsp?thread=107587

For those who just want the code:

template<typename target_type>
class base_class_ptr {

[template fuss snipped]
void* m_a;
table* m_t;
};

template<typename target_T>
template<class T>
typename base_class_ptr<target_T>::table
base_class_ptr<target_T>::functions<T>::table = {
&base_class_ptr<target_T>::template functions<T>::Delete
};

You replace the built-in vtable with you own external, hand-written,
'complicated' vtable. For what reason? Certainly not performance.

Abe
 
P

Pete Becker

Abecedarian said:
Not memory leaks but memory corruption. Undefined behavior in general.

Right. And the true rule is that you should not delete an object of a
derived type through a pointer to a base that does not have a virtual
destructor.
You replace the built-in vtable with you own external, hand-written,
'complicated' vtable. For what reason? Certainly not performance.

No, what he's doing is remembering the derived type, and providing a
function that casts the stored pointer to a pointer to that type in
order to delete it. That's okay, if it's done right.
 
C

christopher diggins

Abecedarian said:
You replace the built-in vtable with you own external, hand-written,
'complicated' vtable. For what reason? Certainly not performance.

Introducing a vtable into an object does hurt performance (whether this
effect is negligble or not depends on your application). It also increase
the amount of memory needed to represent the object. The proposed technique
has the trade-off of only making the pointer bigger.

Christopher Diggins
 
C

christopher diggins

Pete Becker said:
Too many void*'s. The result is that this isn't typesafe:

struct T {};
struct U {};

base_class_ptr<T>( (U*)0 );

The code doesn't enforce the implied requirement that U is derived from T.
That can be fixed by storing the pointer as a T* instead of a void*, as is
(typically) done in TR1's shared_ptr.

I'd also give this thread a different title: "Evading Design Decisions:
Deleting Types Derived from Classes Without Virtual Destructors". After
all, there's usually a good reason for not providing a virtual destructor.

Thanks for pointing these out to me.

- Christopher
 
C

christopher diggins

I would avoid using void.
The following class is a safer workaround method, and it's type safe.

template<typename base_type>
class base_class_ptr
{
class BaseContainer{
public:
virtual ~BaseContainer(){}
};
template<typename derive_type>
class Container : public BaseContainer{
derive_type *m_derive_ptr;
public:
Container(derive_type* derive_obj):m_derive_ptr(derive_obj){}
~Container(){delete m_derive_ptr;}
};
public:
template<typename derive_type>
base_class_ptr(derive_type* derive_obj)
:m_base_ptr(derive_obj), m_BaseContainer(new
Container<derive_type>(derive_obj)){}
~base_class_ptr(){delete m_BaseContainer;}
base_type* operator->() {return m_base_ptr;}
private:
base_type *m_base_ptr;
BaseContainer *m_BaseContainer;
};

Example usage:
void SomeFunction()
{
base_class_ptr<MyBaseClass> ptr_a(new MyDerivedClass_a);
ptr_a->TestFunct();
base_class_ptr<MyBaseClass> ptr_b(new MyDerivedClass_b);
ptr_b->TestFunct();
}

This appears to be a cleaner and safer alternative to what I posted, the
only disadvantage I see is the fact that it requires an extra allocation,
which in many cases is not a big deal. Perhaps a solution using placement
new would be even more effective? Anyone up for that challenge?
 
A

Abecedarian

Pete said:
No, what he's doing is remembering the derived type, and providing a
function that casts the stored pointer to a pointer to that type in
order to delete it. That's okay, if it's done right.

Is
m_t->Delete(m_a);

really faster than something like:
(*vtbl[n])(this);

? (ok, one pointer arithmetic less, but ...)

Abe
 
P

Pete Becker

Abecedarian said:
Is
m_t->Delete(m_a);

really faster than something like:
(*vtbl[n])(this);

? (ok, one pointer arithmetic less, but ...)

I'm not sure what you're getting at. m_t->Delete(m_a) calls a template
function. No vtable involved.
 
A

Abecedarian

Pete said:
Abecedarian said:
Is
m_t->Delete(m_a);

really faster than something like:
(*vtbl[n])(this);

? (ok, one pointer arithmetic less, but ...)

I'm not sure what you're getting at. m_t->Delete(m_a) calls a template
function. No vtable involved.

AFAICS, he calls a Delete function through a function pointer.
Confusingly this is done by an object of type 'table'.
WRT performance: C++ compilers usually don't inline function pointers
(AFAIK). But they know and optimize vtables and vtable-calls. There was
an article in the last(?) CUJ (the one with your article) about vitual
function call optimizations. Probably the Diggins solution is even
slower with most current compilers than the 'overhead' of a virtual
function call. But, of course, any performance claim without
substantial measurement is useless.

Abe
 
P

Pete Becker

Abecedarian said:
AFAICS, he calls a Delete function through a function pointer.
Confusingly this is done by an object of type 'table'.
WRT performance: C++ compilers usually don't inline function pointers
(AFAIK). But they know and optimize vtables and vtable-calls.
Okay.

Probably the Diggins solution is even
slower with most current compilers than the 'overhead' of a virtual
function call. But, of course, any performance claim without
substantial measurement is useless.

It doesn't seem likely that deleting objects would be a significant
application bottleneck.
 
L

lilburne

Pete said:
It doesn't seem likely that deleting objects would be a significant
application bottleneck.

In relation to the amount of time it takes the runtime to coalesce the
freeblock chain it wouldn't be significant at all.
 
A

Abecedarian

lilburne said:
In relation to the amount of time it takes the runtime to coalesce the
freeblock chain it wouldn't be significant at all.

So, the assertion that a vitrual destructor hurts performance is plain
wrong.

::A::
 
L

lilburne

Abecedarian said:
So, the assertion that a vitrual destructor hurts performance is plain
wrong.

If the object is in the heap rather than the stack your main performance
hit will be in the freeblock trundling.

I have an application that creates 10,000,000+ objects at a time,
deleting the objects could take 20+ minutes as the VC6 runtime free code
trundles about coalescing the freeblock chain. We resolved the problem
by block allocation and using placement new, so we only have to delete a
few 1000 large blocks rather than 10,000,000+ small blocks. The cleaning
up takes a second or so. In our experience it is operator new that is
expensive as it eventually results in free().

A virtual destructor is going to be more work to do so it will be slower
by a few clock cycles, whether it is significant for your application is
another matter. For us it is not.
 
P

Pete Becker

Abecedarian said:
So, the assertion that a vitrual destructor hurts performance is plain
wrong.

It depends on the meaning of "performance." It can make objects bigger.
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top