[comp.lang.c++] Should ``delete this;'' appear in virtual member functions?

mckenney@fir.itstd.sri.com (Paul E. McKenney) (03/20/90)

I would like to be able to maintain data structures containing pointers
to objects that may have been created by different memory allocators,
and would like the function that deletes these data structures to remain
ignorant of all of the memory allocators.  My first idea was to have
a separate derived class for each type of memory allocator, and to
make delete be a virtual operator, but this is of course not legal.

My second idea is to make a virtual member function that invokes delete,
as follows:

	class blat1 : public blat_base
		{
	public:
		virtual void	delete_me()
			{
			delete this;
			}
		void		operator delete(void *p)
			{
			dealloc_1(p);
			}
		}

This seems to work under g++ version 1.36.4, at least as long as the programmer
is careful not to deallocate a variable of class blat1 that was allocated 
statically or automatically.  Is this sort of thing portable?  Is there some
more elegant way to express the concept of user-defined storage class in C++?

					Thanx, Paul

mckenney@sparkyfs.istc.sri.com (Paul Mckenney) (04/14/90)

This note describes why I want to be able to use ``delete this;'' in
virtual member functions and includes a small program that uses this
trick in the way that I intend to.  This is a summary of what I have
learned in playing with this problem and in discussions with a number
of people, including James Blasius, Gary Aitken, Scott Turner, Scott
Bruns, Robert Selinger, Andrew Koenig, Michael Tiemann, and
Bjarne Stroustrup.

I want to be able to have multiple high-speed free pools for a given
base class (call it ``buf'').  I need high speed because buf is allocated
and deallocated frequently, and I need multiple pools because there are
several entities competing for bufs; by assigning each its own pool,
I prevent starvation.

However, the bufs are deallocated by code that cannot know which pool
a given buf belongs to until runtime.  I do -not- want this code to have
to know explicitly about all the different pools because I do not want to
have to modify this code each time I add a new pool.  Furthermore, I want to
prevent a naive programmer from attempting to free a statically allocated
variable (auto, static, or extern).

This is accomplished by defining a virtual member function as follows:

	virtual void	del() { delete this; }

(I have adopted Gary Aitken's choice of name, ``del'' being easier to type than
the ``delete_me'' that I used in my original posting. :-)

If ``p'' is a pointer to a base class, and if there is a separate derived
class for each storage pool, then:

	p->del();

will return the object pointed to by ``p'' to the proper pool.  Note that
each derived class must redefine ``operator delete'' (and, of course,
``operator new'') to handle the storage pool for that derived class.

Finally, the destructors for all of the classes are protected.  This
means that the destructor can be invoked only from a member or friend
function, which means that any attempt to define an instance of any
of the classes as a variable will get a compiler error.  This is because
C++ invokes the destructor for each variable when that variable goes out
of scope.  Since the destructor is private, it cannot be accessed from
unprivileged code.

This means that the user has to do something creative (such as using
casts) in order to break ``del''.
						Thanx, Paul
PS.  This code is followed by the output of a sample run, a line
     of dashes separates code from output.
----------------------------------------------------------------------------
#include <stream.h>

class buf
	{
public:
			buf() {};
	virtual void	del() = 0;	/* Delete this object.		*/
protected:
	virtual		~buf() {};	/* Prevent a variable of type	*/
					/* buf from being defined.	*/
	};

class buf1 : public buf
	{
public:
			buf1() {};
	virtual void	del();		/* Delete this object.		*/
	static void	*operator new(size_t s);
protected:
	virtual		~buf1() {};	/* Prevent a variable of type	*/
					/* buf from being defined.	*/
	static void	operator delete(void *p);
	class buf1	*next;
	static class buf1 *free;
	};

void buf1::del()
	{
	delete this;
	}

static void *buf1::operator new(size_t s)

	{
	class buf1	*p;

	if (buf1::free == NULL)
		{
		p = ::new buf1;
		cout << "buf1::"
		     << (int)p
		     << " allocating new element.\n";
		return ((void *)p);
		}
	else
		{
		p = buf1::free;
		cout << "buf1::"
		     << (int)p
		     << " reallocating old element.\n";
		buf1::free = p->next;
		return ((void *)p);
		}
	}

static void buf1::operator delete(void *p)
	{
	class buf1	*p1 = (class buf1 *)p;

	cout << "buf1::"
	     << (int)p
	     << " freeing element.\n";
	p1->next = p1->free;
	p1->free = p1;
	}

static class buf1 *buf1::free = NULL;

class buf2 : public buf
	{
public:
			buf2() {};
	virtual void	del();		/* Delete this object.		*/
	static void	*operator new(size_t s);
protected:
	virtual		~buf2() {};	/* Prevent a variable of type	*/
					/* buf from being defined.	*/
	static void	operator delete(void *p);
	class buf2	*next;
	static class buf2 *free;
	};

void buf2::del()
	{
	delete this;
	}

static void *buf2::operator new(size_t s)

	{
	class buf2	*p;

	if (buf2::free == NULL)
		{
		p = ::new buf2;
		cout << "buf2::"
		     << (int)p
		     << " allocating new element.\n";
		return ((void *)p);
		}
	else
		{
		p = buf2::free;
		cout << "buf2::"
		     << (int)p
		     << " reallocating old element.\n";
		buf2::free = p->next;
		return ((void *)p);
		}
	}

static void buf2::operator delete(void *p)
	{
	class buf2	*p1 = (class buf2 *)p;

	cout << "buf2::"
	     << (int)p
	     << " freeing element.\n";
	p1->next = p1->free;
	p1->free = p1;
	}

static class buf2 *buf2::free = NULL;

main()

	{
	class buf1	*p1 = new buf1;
	// class buf1	bfr2;		// gets compiler error, this prevents
					// attempts to delete a variable.
	class buf	*p2;

	cout << "p1->del();\n";
	p1->del();
	cout << "p1 = new buf1;\n";
	p1 = new buf1;
	cout << "p2 = new buf1;\n";
	p2 = new buf1;
	cout << "p2->del();\n";
	p2->del();
	cout << "p2 = new buf2;\n";
	p2 = new buf2;
	cout << "p2->del();\n";
	p2->del();
	cout << "p1->del();\n";
	p1->del();
	}
------------------------------------------------------------------------
buf1::138880 allocating new element.
p1->del();
buf1::138880 freeing element.
p1 = new buf1;
buf1::138880 reallocating old element.
p2 = new buf1;
buf1::139160 allocating new element.
p2->del();
buf1::139160 freeing element.
p2 = new buf2;
buf2::139256 allocating new element.
p2->del();
buf2::139256 freeing element.
p1->del();
buf1::138880 freeing element.