After a while, though, I realized that the constructor declaration wasn't just a compile-time construct like a subrange or enum declaration -- constructors are different from normal methods. Not only do they have special prolog code that sets the VMT pointer, they also have an extra implicit parameter -- a copy of the 16-bit VMT pointer. The combination of an extra argument and extra work means that an empty constructor call takes 2.8 times as long as an empty method call.
When I realized this, I stopped using constructors so freely. No more constructors calling constructors, or using a constructor to reinitialize an existing object! On the other hand, I still thought of constructors as the right and proper way to setup an object, and defined constructors even for methods with no virtual methods.
Until, that is, I started to wonder why my static objects had a VMT pointer in them. Somehow, I had gotten the impression that only objects with virtual methods would have VMT pointers. In fact, any object with constructors, destructors, or virtual methods will have a VMT pointer.
This has two major consequences. In the first place, it wastes memory. Every object will be two bytes too big, and the data segment will contain an ‘empty’ VMT. (Under TP 5.5 and 6.0, an empty VMT takes up four bytes; under TPW, an empty VMT takes eight bytes.) Now, this level of waste is not likely to cause your data segment to overflow or make your program run out of heap, but it certainly will have some impact and certainly should be avoided. The second consequence is that when an object has a VMT pointer, the SizeOf() routine stops being a compile-time constant and starts actually generating code -- and the code will return garbage if you haven't yet called a constructor to set the object's VMT pointer! That is, SizeOf() uses the VMT pointer to read the object's size from its VMT, and if the VMT pointer hasn't been set, the returned size will be essentially random.
After realizing this, I stopped declaring constructors or destructors unless an object also had virtual methods. I still had a third surprise ahead of me, though.
I was using a variable length object something like the following --
type Header = object {some field declarations} end; Body = object (Header) Data: array[0..60000] of byte; constructor Setup(ActualDataSize: word); end;-- and Windows gave me a UAE every time I tried to call the constructor! What was happening was that the VMT pointer wasn't being added to the object until the first constructor, destructor, or virtual declaration was encountered, and it was then being added as the last field. When I used GetMem() to allocate substantially less than 60,000 bytes, the constructor was trying to set the VMT field -- in memory that didn't belong to the object. The cure was to simply move the constructor declaration into the header, so that the VMT pointer came before the variable length portion of the object, not after.
I've derived two rules of thumb from these experiences. First, static objects should never have constructors. Second, be sure to declare at least one constructor, destructor, or virtual method before any variable length data.
Copyright © 1992, Jon Shemitz - jon@midnightbeach.com - html markup 6-30-94.