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)