From: lwloen@rchland.vnet.ibm.com (Larry Loen)
Subject: Small objs & When/whether to overload "new" with statics
Date: Mon, 04 Jan 1993 16:29:41 GMT
Reply-To: lwloen@rchland.vnet.ibm.com
Disclaimer: This posting represents the poster's views, not necessarily those of IBM
Organization: IBM Rochester

Suppose I have a small "helper" class object like this:

class BitRef   {  // handles assign of bits within char (actually, bit) arrays
  int loc;        // bit location (offset) 0 origin, 0 h.o.bit
  char* data;     // pointer to some character (actually bit) array
  static char cvt[9];
  static char cvtn[9];
  //  cvt[] = { 128,64,32,16,8,4,2,1 }; elsewhere
  //  cvtn[]= { 0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE }
  int GetBit() const  { // return bit at current loc
             int i,j; char k; 
             i=loc/8; j=(loc&7);
             k= (data[i]& cvt[j]);
             if (k!=0) return 1; else return 0;
             };    // end GetBit()
 public:
  BitRef(int l,char *d) : loc(l),data(d) {};
  BitRef& operator = (int q) 
           { int i,j; char k;
             i=loc/8; j=(loc&7);
             k=cvt[j];
             if (q&1) 
               data[i]|=k;
             else               
               data[i]&=(cvtn[j]); 
             return (*this); };
  BitRef& operator = (const BitRef &q)  
           { int i; i=q.GetBit();
             (*this)=i; return (*this); };
  // Note:  default copy constructor and destructor are correct
 };  // end BitRef

The point of "BitRef" is to be returned as a temporary in a variety
of "bit" objects.  For instance:

 class halfword  {  // 16 bits on many machines
   char[2] data;
  public
   BitRef operator[] (int i) { return BitRef(i,data); };
   // etc.
};

and then one codes:

  halfword x;
  x = 13;
  x[0]= 1;  // etc.

Now, I am less interested in the particular virtues and vices of 
BitRef per se than in what to do _in general_ with helper objects of
that kind.  Note carefully that in the normal course of events, BitRef
will never be formally declared.  It will arise, normally, as a temporary
in various objects' operator[].  There are many such potential objects.

Optimizing such objects can be important, especially if they "help" 
functions like operator[], because they may appear in inner "for" loops.

I especially wonder about "new" and "delete".

For machines with enough registers, one presumes that the above code 
could work out fairly nicely, as is.  Each data element of an instance 
of BitRef could imaginably have both of its small pieces optimized into 
a register, especially since the compiler, since it creates it, is in 
full control of how long the instances live.

For a PC-type machine, however, such optimization seems unlikely (is
that so?).  Therefore, _if_ the compiler's generation of "temps" uses
new and delete, one would be tempted to add something like the following
to BitRef

  static short int  two[2];
  static BitRef *twop[2];

Their init would set both members of two to "false" and twop to null.
The overloaded new would see if either value of "two" were false.  If
either was, it would know it they not in use and return a non-null value of
the corresponding twop.  Delete would simply check twop and mark the
corresponding member of two "false" if it matched.  If there were more
than two outstanding BitRefs, the normal new/delete would be used.

The upshot of this is that there typically be only two calls to the
::new function and very few to ::delete, because few expressions would
need more than two BitRefs to complete the work.  And, the memory
allocation overhead could be generally dispensed with, which may be worth
a lot of performance.

On the other hand, if the compiler were smart enough to add its temps to
the regular program push/pop stack (thus avoiding new and delete altogether),
there would be no net advantage to the overload, even with no optimization.
Which is typically done?

In the case of BitRef itself, the optimization question is of some interest,
because it may be sensible to separate "loc" into two values in the object,
passing them into the constructor, up front, which may aid run time 
performance at least some of the time (eg halfword[4]), if one did not wipe 
out the gains by introducing added memory allocation by exceeding some 
internal threshhold or other.

Is all/any of this reasonable analysis?

-- 
   Larry W. Loen        |  My Opinions are decidedly my own, so please
                        |  do not attribute them to my employer
Xref: sparky comp.lang.c++:18957 comp.object:4754