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.
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.
> 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.
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.
> 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. :-)
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.
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.
> > 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