The Artima Developer Community
Sponsored Link

Weblogs Forum
Working Around Non-Virtual Destructors

11 replies on 1 page. Most recent reply: May 5, 2005 11:07 PM by Matthew Wilson

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 11 replies on 1 page
Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Working Around Non-Virtual Destructors (View in Weblogs)
Posted: May 2, 2005 12:52 PM
Reply to this message Reply
Summary
It is commonly recommended in C++ to publicly inherit from classes which have virtual destructors, to avoid possible memory leaks. Here I present a pointer class which allows us to polymorphically use a base class without requiring a virtual destructor.
Advertisement
When inheriting from a class which we intend to use polymorphically in C++, it is required that this class has a virtual destructor. Without this, we run into a problem where the derived constructor will not get called. [See http://www.gotw.ca/publications/mill18.htm]

For example consider the following code:

#include <iostream>
using namespace std;

class MyBaseClass {
public:
  MyBaseClass() {
  }
  ~MyBaseClass() {
  }
};

class MyDerivedClass : public MyBaseClass {
public:
  MyDerivedClass() : MyBaseClass() {
    m = new int;
  }
  ~MyDerivedClass() {
    cout << "deleting m" << endl;
    delete m;
  }
private:
  int* m;
};

int main(int argc, char* argv[])
{
  MyBaseClass* p = new MyDerivedClass;
  delete p; // ~MyDerivedClass never called, and m is never deleted :-(
  system("PAUSE");
  return 0;
}
This is a rather nasty and hard to detect problem. The most commonly prescribed solution is to simply declare ~MyBaseClass() virtual. This however introduces a virtual dispatch table into MyBaseClass, which may not be desirable, as it potentially increases the size of the object, and it hurts performance. An alternative solution (which may be completely novel, I don't know) is to use the following specialized pointer class:
  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
  };
The example before can now be rewritten as:
int main(int argc, char* argv[])
{
  base_class_ptr<MyBaseClass>* p(new MyDerivedClass)
  p.Delete(); // ~MyDerivedClass now called, and m is deleted :-)
  system("PAUSE");
  return 0;
}
Even though the code is complicated what is happening is simply that base_class_ptr carries around a pointer to the appropriate Delete function which gets generated by the compiler. In effect the trade-off is that instead of increasing the size of the object, we increase the size of the pointer.

At this point, I am unaware of any other class which accomplishes the same thing. If anyone has any similar prior work they can point me to it would be greatly appreciated.


indranil banerjee

Posts: 38
Nickname: indranil
Registered: Nov, 2004

Re: Working Around Non-Virtual Destructors Posted: May 2, 2005 1:39 PM
Reply to this message Reply
Boost shared_ptr has the following constructor

template<class T> explicit shared_ptr(Y * p);

This can be used where Y is derived from T, and T does not have to have a virtual destructor.

However, I think your technique different from shared_ptr. They create a deleter object from Y which call's Y's destructor at destruction. Whereas you are a storing a function pointer to Y's destructor directly.

This could be a useful lightweight technique for managing pointers in a class hierarchy without requiring virtual destructors or a heavyweight smart pointer like Boost's.

Christopher Diggins

Posts: 1215
Nickname: cdiggins
Registered: Feb, 2004

Re: Working Around Non-Virtual Destructors Posted: May 2, 2005 2:01 PM
Reply to this message Reply
> Boost shared_ptr has the following constructor
>
> template<class T> explicit shared_ptr(Y * p);
>
> This can be used where Y is derived from T, and T does not
> have to have a virtual destructor.

Thank you very much for pointing this out.

David Maisonave

Posts: 1
Nickname: axter
Registered: May, 2005

Re: Working Around Non-Virtual Destructors Posted: May 2, 2005 2:36 PM
Reply to this message Reply
Here's a workaround base pointer class that does not use void, 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();
}

indranil banerjee

Posts: 38
Nickname: indranil
Registered: Nov, 2004

Re: Working Around Non-Virtual Destructors Posted: May 2, 2005 5:06 PM
Reply to this message Reply
> template<typename base_type>
> class base_class_ptr
> {
> class BaseContainer{
> public:
> virtual ~BaseContainer(){}
> };

But we pay for the virtual function call here right?

indranil banerjee

Posts: 38
Nickname: indranil
Registered: Nov, 2004

Re: Working Around Non-Virtual Destructors Posted: May 2, 2005 5:15 PM
Reply to this message Reply
I knew the work around existed in Boost, but I looked up the constructor signature from here :-)

http://www.ootl.org/boost/shared_ptr.hpp.htm

indranil banerjee

Posts: 38
Nickname: indranil
Registered: Nov, 2004

Re: Working Around Non-Virtual Destructors Posted: May 2, 2005 5:34 PM
Reply to this message Reply
I do have one doubt about this whole thread though.

If we've got a class hierarchy that is being used polymorphically throught base class pointers. Then surely there must be virtual functions in the base class and a vtable must exist.

So what are we gaining by keeping the destructor non virtual? All the solutions discussed here seem like homegrown vtables anyway.

Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

Re: Working Around Non-Virtual Destructors Posted: May 2, 2005 6:05 PM
Reply to this message Reply
> Boost shared_ptr has the following constructor
>
> template<class T> explicit shared_ptr(Y * p);
>
> This can be used where Y is derived from T, and T does not
> have to have a virtual destructor.
>
> However, I think your technique different from shared_ptr.
> They create a deleter object from Y which call's Y's
> destructor at destruction. Whereas you are a storing a
> function pointer to Y's destructor directly.
>
> This could be a useful lightweight technique for managing
> pointers in a class hierarchy without requiring virtual
> destructors or a heavyweight smart pointer like Boost's.

This technique's used in STLSoft's scoped_handle. :-)

Keith Ray

Posts: 658
Nickname: keithray
Registered: May, 2003

Re: Working Around Non-Virtual Destructors Posted: May 3, 2005 10:10 AM
Reply to this message Reply
Implementing work-arounds to avoid virtual functions and virtual destructors: is that really worthwhile use of a programmer's time? Sounds like Premature Optimization if it's being done without measuring and profiling, first.

In "Agile Software Development: Principles, Patterns, and Practices" by Robert C. Martin, he describes writing object-oriented hard-real-time software for a (digital?) copier in C++. He said that only in one (or two) places did he need to make a member function non-virtual for performance reasons.

Check out the this post about Smalltalk method-lookup here: http://www.cincomsmalltalk.com/blog/blogView?showComments=true&entry=3291574575

And this post here comparing the performance of c++ virtual methods to Smalltalk method-lookup here: http://wiki.cs.uiuc.edu/VisualWorks/Why+Smalltalk+is+Faster

Robert C. Martin

Posts: 111
Nickname: unclebob
Registered: Apr, 2003

Re: Working Around Non-Virtual Destructors Posted: May 5, 2005 10:40 PM
Reply to this message Reply
It's important to be very careful with classes that have non-virtual destructors. There are no less than three different ways that they can corrupt the heap.

Consider this simple program:
class A {}
class B {}
class C : public A, public B {}
 
main() {
  A* ap = new C();
  B* bp = new C();
  delete ap;
  delete bp;
}

If you are "lucky" this program will exit. Otherwise it will crash due to a corrupted heap. In this case the heap corruption is do to freeing a pointer that is different from the pointer that was allocated (The address of C and the address of either A or B are not identical) It is the virtual destructor that keeps this from happening.

Another possibility is when the size_t variant of operator new and delete are used. Without a virtual destructor the size passed to operator delete may be different from the size passed to operator new.

Advice: Make your destructors virtual unless you absolutely positively definitely can't afford to, and you've already tried and failed.

Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

Re: Working Around Non-Virtual Destructors Posted: May 5, 2005 11:06 PM
Reply to this message Reply
> Advice: Make your destructors virtual unless you
> absolutely positively definitely can't afford to, and
> you've already tried and failed.

Agreed, with the additional advice that they should be defined protected

Matthew Wilson

Posts: 145
Nickname: bigboy
Registered: Jun, 2004

Re: Working Around Non-Virtual Destructors Posted: May 5, 2005 11:07 PM
Reply to this message Reply
Gah! Silly me

> > Advice: Make your destructors virtual unless you
> > absolutely positively definitely can't afford to, and
> > you've already tried and failed.
>
> Agreed, with the additional advice that they should be
> defined protected

[the second half of that sentence is]

if they're non-virtual

Flat View: This topic has 11 replies on 1 page
Topic: Testing Invariants in C++ Previous Topic   Next Topic Topic: Two Stage Construction in C++ versus Initializing Constructors


Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us