From: jamshid@ut-emx.uucp (Jamshid Afshar)
Newsgroups: comp.lang.c++
Subject: Re: help?? is their a method for solving dynamic allocation problemes in c++
Summary: example of fairly painless operator new/delete overloading
Keywords: overloading, new, delete, templates
Date: 1 Sep 92 19:11:24 GMT
Reply-To: jamshid@emx.utexas.edu
Organization: The University of Texas at Austin; Austin, Texas

In article <1992Aug19.203838.20501@bmw.mayo.edu> staniszewski@mayo.edu writes
>The problem I am looking at is to trackdown where my dynamic memory
>problems are using new and delete. I have a nice set of replacement
>routines for the alloc routines [...but it...] seems possible to do this
>for new, since the global operator can be overloaded, but this is a little
>awkward.  AND the global delete cannot be overloaded PERIOD.

I previously replied to this article incorrectly.  I thought Stan
just wanted to override his compiler's global
   void* operator new(size_t)
and
   void operator delete(void*)
functions since it's common to replace them with a debugging
version.  Actually, he wrote me that he truely meant "overload" (as
in using operator new with a different syntax -- the "awkward" part).
He also wrote that on one system he works with (VMS?) the global
operator delete() cannot be replaced anyway because it is in a DLL.
Is this true -- are there any workarounds?  Will ANSI C++ *require*
that implementations allow the replacement of these functions?  Anyway,
back to the original question...

First, remember that general use of an overloaded new is not only a
pain to type but dangerous because you must be sure that you do not
delete using the regular delete operator.  Since deletion is not
always done in the same function or part of the code, and in fact may
be done in multiple places, it wasn't felt that allowing the
overloading of the delete operator would buy much over requiring
deletion by a user-defined function (read ARM 5.3.4).

So, assuming you want to go through with this, the syntax is bearable
if your compiler supports templates.  Let's say you had a memory
package which also logged data such as the file/line where the
new/delete was being performed:

   void* operator new(size_t size, char* filename, int line);
   void /*operator*/ delete_(void* p, char* filename, int line);
         // can't *really* overload operator delete()

To new an object you simply do:

   Foo* p = new (__FILE__,__LINE__) Foo;  // instead of 'Foo* p = new Foo;'

Deleting is harder because you must explicitly call the destructor.
A function template helps alot.

   template<class T> inline
   void mydelete(T* p, char* filename, int line) {
      p->~T();    // BC++ 3.1 requires p->T::~T() for built-in types!
      delete_((void*)p, filename, line);     // return bytes to free store
   }
   ...
   mydelete(p, __FILE__, __LINE__);    // instead of 'delete p;'

All this is easy compared to what you have to go through with arrays
(and I haven't even considered multi-dim arrays).  You must
explicitly store the size of the array so you know how many
destructors to call.  Normally (and if you only needed to overload
the new operator) this is handled by the implementation.  In my
example I'll store the size at the beginning of the allocated
block.  So instead of allocating a T[n], I will allocate a
variable-sized ArrayBlock struct:

   template<class T>    // amount actually allocated for array
   class ArrayBlock {
      size_t num;   // actual number of elements in arr
      T arr[1];     // actual size will be 'num'
   public:
      static T* new_arr(size_t num, char* filename, int line);
      static void delete_arr(T* arr, char* filename, int line);
   };

The static member functions actually allocate and deallocate the
ArrayBlock.  They handle the storing of the size, constructing and
destroying the elements, and adjusting the 'T*' to/from an
'ArrayBlock<T>*'.  A helper function:

   template<class T> inline
   void delete_arr(T* arr, char* filename, int line)
      { ArrayBlock<T>::delete_arr(arr, filename, line); }

does the deletion so you don't have to specify the type.  Your code
will therefore look like:

   Foo* p = ArrayBlock<Foo>::new_arr(10, __FILE__, __LINE__);
            // instead of 'Foo* p = new Foo[10];'
   ...
   delete_arr(p);    // instead of 'delete [] p;'

Any questions?  I'm appending the complete code which uses macros to
make the syntax more familiar (though I'm not sure I would recommend
using them for this same reason).  I haven't done much testing beyond
compiling it with BC++ 3.1.  It probably doesn't compile under BC++
3.0.  Cfront 3.x and gcc 2.x should work, though they may have bugs
requiring code changes.  Let me know if I've overlooked anything.

Jamshid Afshar
jamshid@emx.utexas.edu

////---cut here---////
#include <stddef.h>     // for size_t, offsetof()

/***** these two functions do the debugging/statistics gathering *****/

void* operator new(size_t size, char* filename, int line);
void delete_(void* p, char* filename, int line);

template<class T>    // helper for DELETE
inline void mydelete(T* p, char* filename, int line) {
#ifndef __BORLANDC__
   p->~T();
#else    // Borland 3.1 compiler bug (very bad if ~T() virtual)
   p->T::~T();
#endif
   delete_((void*)p, filename, line);
}

/***** use these two macros in your code to allocate single objects *****/

#define NEW(TYPE) new (__FILE__,__LINE__) TYPE
#define DELETE(p) mydelete(p, __FILE__, __LINE__)


/***** new/delete arrays is trickier *****/

// might be defined in your <new.h>, but I don't think it's supposed to be
inline void* operator new(size_t, void* p) { return p; }

template<class T>
class ArrayBlock {
   size_t num;   // number of elements in array has to be stored for delete
   T arr[1];     // actually could be more elements in array
public:
   static T* new_arr(size_t num, char* filename, int line) {
      ArrayBlock<T>* block = (ArrayBlock<T>*) operator
               new(sizeof(ArrayBlock<T>)+sizeof(T)*(num-1), filename, line);
      block->num = num;
      // now construct each element using default ctor
      for (int i=0; i<num; i++)
         new ((void*)(&block->arr[i])) T;
      return block->arr;
   }

   static void delete_arr(T* arr, char* filename, int line) {
      if (!arr) return;
      ArrayBlock<T>* block = (ArrayBlock<T>*)((char*)arr-offsetof(ArrayBlock<T>, arr));
      size_t num = block->num;
      while (num>0)
#ifndef __BORLANDC__
         arr[--num].~T();   // destroy each element in reverse order
#else  // Borland 3.1 compiler bug
         (&arr[--num])->T::~T();
#endif
      delete_((void*)block, filename, line);
   }
};

template<class T> inline
void delete_arr(T* arr, char* filename, int line)
   { ArrayBlock<T>::delete_arr(arr, filename, line); }

/***** use these two macros in your code to allocate array of objects *****/

#define NEWARR(TYPE, NUM) ArrayBlock<TYPE>::new_arr(NUM, __FILE__, __LINE__)
#define DELETEARR(p) delete_arr(p, __FILE__, __LINE__)


/***** These are the functions you provide *****/

#include <iostream.h>
#include <stdlib.h>

void* operator new(size_t size, char* filename, int line) {
   cout << "new(" << size << ", " << filename << ", " << line << ")\n";
   return malloc(size);
}

void delete_(void* p, char* filename, int line) {
   cout << "free(" << p << ", " << filename << ", " << line << ")\n";
   free(p);
   }


/***** This is your code *****/

#include <iostream.h>

class Foo { int i; public: Foo(); ~Foo(); };

Foo::Foo() {
   cout << "Foo()\n";
}

Foo::~Foo() {
   cout << "~Foo()\n";
}

int main() {
   char* p = NEW(char);          // char* p = new char;
   char* arr = NEWARR(char,8);   // char* arr = new char[8];
   DELETE(p);                    // delete p;
   DELETEARR(arr);               // delete [] arr;

   cout << "--------------\n\n";

   int n = 8;
   Foo* foop = NEW(Foo);         // Foo* p = new Foo;
   Foo* fooarr = NEWARR(Foo,n);  // Foo* arr = new Foo[8];
   DELETE(foop);                 // delete p;
   DELETEARR(fooarr);            // delete [] arr;

   return 0;
}
////---cut here---////
Newsgroups: comp.lang.c++