Search code examples
delphirecorddelphi-2007reference-counting

String lifetime management, in records


I am working on getting rid of shortstring. One of the many places shortstring is currently used within our programs is in records. Alot of these records are kept in AVL trees.

The AVL tree used is a generic one, holding a pointer to a number of bytes (ElemSize), which have worked well so far. The memory for each record in the AVL tree is allocated with GetMem, and copied with Move. However, with string being a pointer to a reference-counted structure, copying back the memory to a record no longer works, as the sting referenced is often freed (automatically by reference count). With only a pointer and a size of the "data block", I assume it is not possible to have the reference count of the strings increased.

I'm looking for a way to get the reference count of the stings to be taken into account when storing the record in a AVL tree.

Can I pass the record type to the tree constructor, then cast the pointer to this type and thus get the references increased? Or a similar fix, where I can isolate the changes to primarily be in the AVL unit and calls to it's constructor.

Current code for allocation of space to store the record in AVL; XData is a pointer to the record to be stored:

New(RootPtr); { create new memory space }
GetMem(RootPtr^.TreeData, ElemSize);
WITH RootPtr^ DO BEGIN
    { copy data }
    Move(XData^, RootPtr^.TreeData^, ElemSize);

Solution

  • In essence the question you are asking is:

    How can I allocate, copy and deallocate a record when all I know about its type is its size?

    The simple answer is that you can use GetMem, Move and FreeMem provided that the record does not contain managed types. You wish to work with records that contain Delphi strings, which are managed. And so your current approach using GetMem and Move does not suffice.

    There are plenty of ways to solve this. You could write your own code to do reference counting, so long as you knew where in the record the managed types were. I don't recommend this. You could make your user data be a class and use polymorphism to help.

    The option I'd like to discuss continues to support records and indeed allows the user to choose whatever type they like. The reasoning is as follows:

    If the type contains managed types, then operating on it requires knowledge of the type. If the tree is to be generic, then it cannot have that knowledge. Ergo, the knowledge must be supplied by the user of the tree.

    This leads you to events. Let the tree offer events that the user can supply handlers for. The types would look like this:

    type
      PTreeNodeUserData = type Pointer;
    
      TTreeNodeCreateUserDataEvent = function: PTreeNodeUserData of object;
      TTreeNodeDestroyUserDataEvent = procedure(Data: PTreeNodeUserData) of object;
      TTreeNodeCopyUserDataEvent = procedure(Source, Dest: PTreeNodeUserData) of object;
    

    Then you can arrange for your tree to publish events with these types that the user can subscribe to.

    The point being that this allows the user of the tree to supply the missing knowledge about the user data type.