March 07, 2003 Tomasz Kukielka tom@abracode.com Classes and naming convention: AMacPtr for classic Macintosh memory block allocated with NewPtr APtr (= AMacPtr) AMacHandle for classic Macintosh memory block allocated with NewHandle AHandle (= AMacHandle) AMacResHandle based on AMacHandle - this one is similar to AMacHandle but can also be a resource handle AResHandle (= AMacResHandle); AStdMalloc for memory allocated with std::malloc AMalloc (= AStdMalloc) AStdNew for objects allocated with "new" AStdArrayNew for objects allocated with "new []" AStdObjArrayNew for array allocated with "new []" but also "owning" objects stored in the array Objects are freed on desctruction of the array if non-null disposal proc was provided by caller. The dispose proc protypes is: typedef void (*ADisposeProc)(void *inObj); The pointer passed to DisposeProc is of type T* It is a pointer to stored object, not the object itself (which may be a structure) ----------------------------------------------------------------------------- All wrappers are template-based. All sizes given and returned are the COUNT OF "T" OBJECTS It means that: AMacPtr charObj(100); allocates 100 bytes, but: AMacPtr shortObj(100); allocates 200 bytes! In other words: charObj.GetSize() == shortObj.GetSize() but ::GetPtrSize((Ptr)(char *)charObj) != ::GetPtrSize((Ptr)(short *)shortObj) ----------------------------------------------------------------------------- AMemoryCommon.h defines several enumarations used in all memory classes. I decided to use enumerations instead of bool for passing some options to constructors becuase bool type can easily be cast into SInt32 and vice versa leading to ambiguous access and compiler errors. Some constructors take an additional param telling if the memory space referenced by the pointer or Handle is going to be copied (deep) or just the reference to existing memory block is stored (shallow) typedef enum { kMemObj_ShallowCopy, kMemObj_DeepCopy } EMemObjCopyType; Ownership type. Could have been Boolean but it is better if it is enum: typedef enum { kMemObj_NotOwned, kMemObj_Owned } EMemObjOwnershipType; Additional parameter for constructors which allocate memory: typedef enum { kMemObj_DontClearMemory, //default in most cases kMemObj_ClearMemory //zero memory on allocation } EMemObjClearOption; ----------------------------------------------------------------------------- The idea of providing a common base class for all memory wrappers was very tempting. However, after closer look I found that it brings a lot of problems and produces bigger and slower objects. Consult Scott Meyers' book "More Effective C++", especially the frgment in chapter "Efficiency": "ITEM 24. Understand the Costs of Virtual Functions, Multiple Inheritance, Virtual Base Classes, and RTTI." But the last nail to the coffin of common base class was that the base class destructor will not call virtual DisposeSelf() functions in derived classes. Consult: ANSI C++ chapter 12.7, point 3 ftp://ftp.research.att.com/dist/c++std/WP/CD2/body.pdf http://www.codeproject.com/cpp/ANSI-cpp-dec96/special.asp#class.cdtor When a virtual function is called directly or indirectly from a con- structor (including from the mem-initializer for a data member) or from a destructor, and the object to which the call applies is the object under construction or destruction, the function called is the one defined in the constructor or destructor's own class or in one of its bases, but not a function overriding it in a class derived from the constructor or destructor's class, or overriding it in one of the other base classes of the most derived object (_intro.object_). If the virtual function call uses an explicit class member access (_expr.ref_) and the object-expression refers to the object under con- struction or destruction but its type is neither the constructor or destructor's own class or one of its bases, the result of the call is undefined. ----------------------------------------------------------------------------- These wrapper memory classes can be described as smart pointers. Some ideas are stolen from Scott Meyers' "More Effective C++". Fragments related to smart pointers are available online: "Smart pointers, Part 1" C++ Report, April 1996. http://www.aristeia.com/Papers/C++ReportColumns/apr96.pdf "Smart pointers, nullness, and type conversions" C++ Report, June 1996. http://www.aristeia.com/Papers/C++ReportColumns/jun96.pdf "Smart pointers: automating inheritance and const conversions" C++ Report, September 1996. http://www.aristeia.com/Papers/C++ReportColumns/sep96.pdf "Refinements to smart pointers" C++ Report, November-December 1996. http://www.aristeia.com/Papers/C++ReportColumns/novdec96.pdf ----------------------------------------------------------------------------- "bool operator() const" and "bool operator!() const" Some people like to write: if(ptr) { ; } if(!ptr) { ; } implicitly casting pointers to booleans. It works, but I HATE it. It is not logically justifiable and makes the code harder to read. To make it even worse the same people name their variables foo and then they write: if(foo) { do_something_really_really_smart(foo); } Reading such code you have no idea if foo is bool, long, void * or Handle. You have to scroll several screens up to check the declaration. Why is it so difficult to write if(foo != NULL) ? Becase of this I did not implement "bool operator() const" nor "bool operator!() const" for implicit convertion of pointers to bool in any of these classes. Implementing such operator has other drawbacks as well, check Scott Meyers' articles. ----------------------------------------------------------------------------- I added a AStdNew class a'la std:auto_ptr and PowerPlant::StDeleter (against David's recommendation:-)) for 3 reasons: 1. Completeness. Greg Dow was not afraid to ad StDeleter to PowerPlant. (Dont' get me wrong: I am not a Greg Dow by any means, in fact I was learning C++ with PowerPlant). 2. auto_ptr uses a different approach: pointer cannot be disowned and kept at the same time. With AStdNew you can keep the pointer which you do not own as is the case of PowerPlant::StDeleter and the rest of memory classes I designed for ACCELA 3. A custom DisposeProc is provided for each wrapper class. It is used as a callback proc for freeing arrays of objects - this proc is used to free each object before the array itself is killed.