Connecting Tech Pros Worldwide Forums | Help | Site Map

Proposed work-around for missing virtual destructors

christopher diggins
Guest
 
Posts: n/a
#1: Jul 23 '05
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/viewpo...?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
};

--
Christopher Diggins
Object Oriented Template Library (OOTL)
http://www.ootl.org


Pete Becker
Guest
 
Posts: n/a
#2: Jul 23 '05

re: Proposed work-around for missing virtual destructors


christopher diggins wrote:[color=blue]
> 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.[/color]

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.

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Axter
Guest
 
Posts: n/a
#3: Jul 23 '05

re: Proposed work-around for missing virtual destructors


christopher diggins wrote:[color=blue]
> I posted to my blog a special pointer class work-around for[/color]
inheriting from[color=blue]
> base classes without virtual destructors. I was wondering if there is[/color]
any[color=blue]
> other similar work, and whether there are any problems with my[/color]
proposed[color=blue]
> approach. Thanks in advance!
> See http://www.artima.com/weblogs/viewpo...?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
> };
>
> --
> Christopher Diggins
> Object Oriented Template Library (OOTL)
> http://www.ootl.org[/color]


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();
}

Abecedarian
Guest
 
Posts: n/a
#4: Jul 23 '05

re: Proposed work-around for missing virtual destructors


christopher diggins wrote:[color=blue]
> It is commonly recommended in C++ to publicly inherit from
> classes which have virtual destructors, to avoid possible memory
> leaks.[/color]

Not memory leaks but memory corruption. Undefined behavior in general.
[color=blue]
> I posted to my blog a special pointer class work-around for[/color]
inheriting from[color=blue]
> base classes without virtual destructors. I was wondering if there is[/color]
any[color=blue]
> other similar work, and whether there are any problems with my[/color]
proposed[color=blue]
> approach. Thanks in advance!
> See http://www.artima.com/weblogs/viewpo...?thread=107587
>
> For those who just want the code:
>
> template<typename target_type>
> class base_class_ptr {[/color]

[template fuss snipped]
[color=blue]
> 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
> };[/color]

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

Abe

Pete Becker
Guest
 
Posts: n/a
#5: Jul 23 '05

re: Proposed work-around for missing virtual destructors


Abecedarian wrote:[color=blue]
> christopher diggins wrote:
>[color=green]
>>It is commonly recommended in C++ to publicly inherit from
>>classes which have virtual destructors, to avoid possible memory
>>leaks.[/color]
>
>
> Not memory leaks but memory corruption. Undefined behavior in general.
>[/color]

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.
[color=blue]
>
> You replace the built-in vtable with you own external, hand-written,
> 'complicated' vtable. For what reason? Certainly not performance.
>[/color]

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.

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
christopher diggins
Guest
 
Posts: n/a
#6: Jul 23 '05

re: Proposed work-around for missing virtual destructors


"Abecedarian" <abecedarian@spambob.com> wrote in message
[color=blue]
> You replace the built-in vtable with you own external, hand-written,
> 'complicated' vtable. For what reason? Certainly not performance.[/color]

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



christopher diggins
Guest
 
Posts: n/a
#7: Jul 23 '05

re: Proposed work-around for missing virtual destructors


"Pete Becker" <petebecker@acm.org> wrote in message
news:t8idnUSlC9Pj_uvfRVn-tw@giganews.com...[color=blue]
> christopher diggins wrote:[color=green]
>> 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.[/color]
>
> 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.[/color]

Thanks for pointing these out to me.

- Christopher


christopher diggins
Guest
 
Posts: n/a
#8: Jul 23 '05

re: Proposed work-around for missing virtual destructors


> I would avoid using void.[color=blue]
> 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();
> }[/color]

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?

--
Christopher Diggins
Object Oriented Template Library (OOTL)
http://www.ootl.org


Abecedarian
Guest
 
Posts: n/a
#9: Jul 23 '05

re: Proposed work-around for missing virtual destructors


Pete Becker wrote:[color=blue]
> Abecedarian wrote:[color=green]
> >
> > You replace the built-in vtable with you own external,[/color][/color]
hand-written,[color=blue][color=green]
> > 'complicated' vtable. For what reason? Certainly not performance.[/color]
>
> 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.[/color]

Is
m_t->Delete(m_a);

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

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

Abe

Pete Becker
Guest
 
Posts: n/a
#10: Jul 23 '05

re: Proposed work-around for missing virtual destructors


Abecedarian wrote:[color=blue]
>
> Is
> m_t->Delete(m_a);
>
> really faster than something like:
> (*vtbl[n])(this);
>
> ? (ok, one pointer arithmetic less, but ...)
>[/color]

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

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Abecedarian
Guest
 
Posts: n/a
#11: Jul 23 '05

re: Proposed work-around for missing virtual destructors


Pete Becker wrote:[color=blue]
> Abecedarian wrote:[color=green]
> >
> > Is
> > m_t->Delete(m_a);
> >
> > really faster than something like:
> > (*vtbl[n])(this);
> >
> > ? (ok, one pointer arithmetic less, but ...)
> >[/color]
>
> I'm not sure what you're getting at. m_t->Delete(m_a) calls a[/color]
template[color=blue]
> function. No vtable involved.[/color]

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

Pete Becker
Guest
 
Posts: n/a
#12: Jul 23 '05

re: Proposed work-around for missing virtual destructors


Abecedarian wrote:[color=blue]
>
> 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.[/color]

Okay.
[color=blue]
> 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.
>[/color]

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

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
lilburne
Guest
 
Posts: n/a
#13: Jul 23 '05

re: Proposed work-around for missing virtual destructors




Pete Becker wrote:
[color=blue][color=green]
>>[/color]
>
> It doesn't seem likely that deleting objects would be a significant
> application bottleneck.
>[/color]

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

Abecedarian
Guest
 
Posts: n/a
#14: Jul 23 '05

re: Proposed work-around for missing virtual destructors


lilburne wrote:[color=blue]
> Pete Becker wrote:[color=green]
> > It doesn't seem likely that deleting objects would be a significant[/color][/color]
[color=blue][color=green]
> > application bottleneck.[/color]
>
> In relation to the amount of time it takes the runtime to coalesce[/color]
the[color=blue]
> freeblock chain it wouldn't be significant at all.[/color]

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

::A::

lilburne
Guest
 
Posts: n/a
#15: Jul 23 '05

re: Proposed work-around for missing virtual destructors




Abecedarian wrote:
[color=blue]
> lilburne wrote:
>[color=green]
>>Pete Becker wrote:
>>[color=darkred]
>>>It doesn't seem likely that deleting objects would be a significant[/color][/color]
>
>[color=green][color=darkred]
>>>application bottleneck.[/color]
>>
>>In relation to the amount of time it takes the runtime to coalesce[/color]
>
> the
>[color=green]
>>freeblock chain it wouldn't be significant at all.[/color]
>
>
> So, the assertion that a vitrual destructor hurts performance is plain
> wrong.
>[/color]

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.

Pete Becker
Guest
 
Posts: n/a
#16: Jul 23 '05

re: Proposed work-around for missing virtual destructors


Abecedarian wrote:[color=blue]
>
> So, the assertion that a vitrual destructor hurts performance is plain
> wrong.
>[/color]

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

--

Pete Becker
Dinkumware, Ltd. (http://www.dinkumware.com)
Closed Thread