[comp.lang.c++] Overloaded operator new VS. inheritance

keffert@jacobs.cs.orst.edu (Thomas Keffer) (01/11/91)

I've been using overloaded new and delete operators
to implement a "small object pool" to speed the
allocation and deallocation of small objects from the heap.
I have been running into problems with inheritance and want to
propose a simple language extension to deal with the problem.
Here's an example:

#define STASHSIZE 10

static void* stash[STASHSIZE];  // The pool of "free" objects.
static short nstash = 0;        // The number of objects in the pool

class Small {
...
public:
  void* operator new(size_t sz){
    // If the pool is empty, use the global allocator:
    return nstash ? stash[--nstash] : ::new char[sz];
  }
  void operator delete(void* b){
    // If the pool is full, call the global delete operator.
    if(nstash>=STASHSIZE)
      ::delete b;
    else
      stash[nstash++] = b;
  }
};

So what's the problem?   

Inheritance.

If another (larger) object is inherited from class "Small", it will
inherit Small's new and delete operators.  The pool will be full of
objects of the wrong size, resulting in an error.  Because the size of
the object is available to Small::operator new, I could fix this by
maintaining an array of free objects, one slot for each object size
(up to some certain maximum beyond which ::new is called).  Given the
size of the object, I pick the proper slot and return an appropriately
sized object.

But then what about the delete operator?  The size of the object is
NOT passed to Small::operator delete.  I don't know where to put the
recently freed object.

It appears that to use overloaded new and delete operators safely you
must either:

1)  remember the object's size within the object itself (requiring
extra memory).

or

2) give the object only private constructors, disallowing inheritance.


An easy solution to the problem would be to have Small::operator
delete pass the size of the object --- it's known at the point of
call, even for objects with virtual destructors.

Comments?

-tk

---
Tom Keffer          | uucp:      ...!orstcs!rwave!keffer
Rogue Wave          | Internet:  keffer%rwave.uucp@cs.orst.edu
P.O. Box 2328	    | BIX:       tkeffer
Corvallis, OR 97339 | (503) 745-5908

philip@pescadero.Stanford.EDU (Philip Machanick) (01/11/91)

In article <1991Jan10.195541.6003@usenet@scion.CS.ORST.EDU>, keffert@jacobs.cs.orst.edu (Thomas Keffer) writes:
|> I've been using overloaded new and delete operators
|> to implement a "small object pool" to speed the
|> allocation and deallocation of small objects from the heap.
|> I have been running into problems with inheritance and want to
|> propose a simple language extension to deal with the problem.
|> Here's an example:
[example deleted]
[why passing size to delete would be useful]
|> An easy solution to the problem would be to have Small::operator
|> delete pass the size of the object --- it's known at the point of
|> call, even for objects with virtual destructors.

ARM p 283: this feature is already available. You can either have operator
delete with one argument (void*) or two (void*, size_t).

-- 
Philip Machanick
philip@pescadero.stanford.edu

mat@mole-end.UUCP (Mark A Terribile) (01/12/91)

> I've been using overloaded new and delete operators to implement a "small
> object pool" to speed the allocation and deallocation of small objects from
> the heap. I have been running into problems with inheritance and want to
> propose a simple language extension to deal with the problem.

> So what's the problem?   
> Inheritance.
 
> But then what about the delete operator?  The size of the object is
> NOT passed to Small::operator delete.  I don't know where to put the
> recently freed object.

Well, actually, it is passed as the (optional) second argument.  What
you need is to ensure that you get the size for the actual object you
have.
 
> It appears that to use overloaded new and delete operators safely you
> must either:

> 1)  remember the object's size within the object itself (requiring
> extra memory).
> 2) give the object only private constructors, disallowing inheritance.


> An easy solution to the problem would be to have Small::operator
> delete pass the size of the object --- it's known at the point of
> call, even for objects with virtual destructors.

You've got it now: virtual destructors.  The destructor for the actual
(complete and exact) type DOES know the actual size size of the object.
If the destructor is virtual, then the operation of  operator delete()
will (effectively) be virtual as well.  (Yes, I know that this is a
mistake to speak of a static function as being virtual ... but I write
`effectively.')

It probably IS a mistake to allow inheritance AND new and delete without
a virtual destructor; a derived class which does not inherit a virtual
destructor and IS used with operator new, or for which there is an operator
delete, probably ought to generate a compiler warning of some sort.

This gets rediscovered periodically; it seems like a FAQ item, if ever an
FAQ++ there is.
-- 

 (This man's opinions are his own.)
 From mole-end				Mark Terribile

jimad@microsoft.UUCP (Jim ADCOCK) (01/16/91)

In article <1991Jan10.195541.6003@usenet@scion.CS.ORST.EDU> keffert@jacobs.cs.orst.edu (Thomas Keffer) writes:
>I've been using overloaded new and delete operators
....
>But then what about the delete operator?  The size of the object is
>NOT passed to Small::operator delete.  I don't know where to put the
>recently freed object.

According to ARM, page 283, you can declare an overloaded operator
delete with two parms:

class Y {
	// ...
	void operator delete(void*, size_t);
};

in which case the second parameter will be filled in by the compiler
to indicate the size of the object to be deleted.  The size passed by
the compiler is determined either from the static type of the pointer,
or by the destructor call, if any.  Hope this helps.

tom@sco.COM (Tom Kelly) (01/18/91)

In article <1991Jan10.224933.7518@Neon.Stanford.EDU> philip@pescadero.stanford.edu writes:
>In article <1991Jan10.195541.6003@usenet@scion.CS.ORST.EDU>, keffert@jacobs.cs.orst.edu (Thomas Keffer) writes:
>|> I've been using overloaded new and delete operators
>
>ARM p 283: this feature is already available. You can either have operator
>delete with one argument (void*) or two (void*, size_t).
>

Beware: there seems to be a cfront bug lurking here: (this has
been posted before)

The second argument to delete is supposed to be the size in bytes of the
object being deleted.  The ARM, p. 283 gives the conditions under which
this size will be correct for inherited classes.

In the example below, the size passed to the destructor is always
only the size of the base class, even when the pointer points
to a derived class.

Note that adding a virtual destructor to the base class fixes
the problem.

#include <stdio.h>
#include <malloc.h>

class base {
public:
	void *operator new(size_t s);
	void operator delete(void *, size_t);
#ifdef FIX
	virtual ~base() {};
#endif
private:
	int key;
};

void *
base::operator new(size_t s)
{
	void *p;

	printf("calling operator new for base %d\n", s);
	p = malloc(s);
	printf("allocated to %x\n", p);
	return p;
}

void
base::operator delete(void *p, size_t s)
{
	printf("called operator delete for base %x size %d\n", p, s);
	free(p);
	return;
}

class derived: public base {
public:
	int x;
	int y;
	derived(int a, int b): x(a), y(b) {};
};

class derived2: public base {
public:
	int a;
	char b[10];
};

derived d(1, 2);

main()
{
	derived *dp;
	base *bp;

	printf("%d %d\n", d.x, d.y);

	dp = new derived(5, 6);
	printf("%d %d\n", dp->x, dp->y);

	bp = new base;

	derived2 *dp2 = new derived2;

	delete dp;
	delete dp2;
	delete bp;
}

jbuck@galileo.berkeley.edu (Joe Buck) (01/21/91)

In article <1991Jan17.200447.6794@sco.COM>, tom@sco.COM (Tom Kelly) writes:
> In the example below, the size passed to the destructor is always
> only the size of the base class, even when the pointer points
> to a derived class.
 
> Note that adding a virtual destructor to the base class fixes
> the problem.

Ah, but that is correct behavior on the part of the compiler.  Without
the virtual destructor, the compiler has absolutely no information
that the pointer is really to a derived class (what if there are no
virtual functions defined in the base class?  Then the code has no
way, short of ESP, to tell that the pointer is really to a derived
class).

In short: ALWAYS make the destructor virtual if you plan to derive
from the class, and if at any point in your code you have

	Base* b;
	...
	delete b;

but b may point to an object of a different class (shorthand version
of this rule: always make the baseclass destructor virtual unless
you will never derive from that class).

I expect that you'll see identical behavior on any C++ compiler.


--
Joe Buck
jbuck@galileo.berkeley.edu	 {uunet,ucbvax}!galileo.berkeley.edu!jbuck	

mjv@objects.mv.com (Michael J. Vilot) (01/28/91)

Tom Keffer commented on the subtleties induced by inheriting operators new and
delete, and proposed a language extension to deal with the problem.

First, let me point out that the problem he cited does not require a language
extension to solve it.  Tom's example failed to take advantage of an existing
feature of overloading operator delete:  specifically, the form taking a second
argument of type size_t (ARM, p. 283):
    void operator delete(void*, size_t);
If the classes involved in inheritance provide a virtual destructor, the right
size should get passed to delete.

Second, I applied exactly this technique to the Managed and Controlled forms of
the C++ Booch Components library.  It works just fine, at the cost of a slightly
more sophisticated free list manager (it keeps the free lists sorted by size).

--
Mike Vilot,  ObjectWare Inc, Nashua NH
mjv@objects.mv.com  (UUCP:  ...!decvax!zinn!objects!mjv)