How to properly Use Dynamic (variable-length) Arrays in FB UDTs

Forum for discussion about the documentation project.
fxm
Posts: 8167
Joined: Apr 22, 2009 12:46
Location: Paris suburb, FRANCE

How to properly Use Dynamic (variable-length) Arrays in FB UDTs

Postby fxm » May 26, 2018 14:52

When declaring a dynamic (variable-length) array member in a Type, 'Any' must be always specified in place of the array bounds in order to create a dynamic array with a certain amount of dimensions that is determined based on the number of 'Any' specified.
In FreeBASIC, Type data structures must ultimately be fixed-size, such that the compiler knows how much memory to allocate for objects of that Type.
Nevertheless, a Type may contain dynamic (variable-length) field members: strings and even arrays.

However, the string's/array's data will not be embedded in the Type directly. Instead, the Type will only contain a string/array descriptor structure, which FreeBASIC uses behind the scenes to manage the dynamic string/array data in the heap.
Therefore for sizing the structure of the array descriptor in the Type, a dynamic array member must be always declared by using 'Any' in place of the array bounds, in order to fix the amount of dimensions (this fixing the descriptor size).

1)
When the compiler build a default copy-constructor and a default copy-assignment operator for such a Type (having dynamic array members), it includes all code for sizing the destination array and copying the data from the source array, if needed.

Example 1:
    Automatic array sizing and copying by the compiler:

    Code: Select all

    Type UDT
      Dim As Integer array(Any)
    End Type

    Dim As UDT u1, u2

    Redim u1.array(1 To 9)
    For I As Integer = Lbound(u1.array) To Ubound(u1.array)
      u1.array(I) = I
    Next I
     
    u2 = u1
    For I As Integer = Lbound(u2.array) To Ubound(u2.array)
      Print u2.array(I);
    Next I
    Print

    Dim As UDT u3 = u1
    For I As Integer = Lbound(u3.array) To Ubound(u3.array)
      Print u3.array(I);
    Next I
    Print

    Sleep

    Code: Select all

     1 2 3 4 5 6 7 8 9
     1 2 3 4 5 6 7 8 9
2)
If the user want to specify his own copy-constructor and copy-assignment operator (to initialize additional complex field members for example), the above automatic array sizing and copying by compiler is broken.

Example 2:
    Automatic array sizing and copying by the compiler broken by an explicit copy-constructor and copy-assignment operator:

    Code: Select all

    Type UDT
      Dim As Integer array(Any)
      'user fields
      Declare Constructor ()
      Declare Constructor (Byref u As UDT)
      Declare Operator Let (Byref u As UDT)
    End Type

    Constructor UDT ()
      'code for user fields in constructor
    End Constructor

    Constructor UDT (Byref u As UDT)
      'code for user fields in copy-constructor
    End Constructor

    Operator UDT.Let (Byref u As UDT)
      'code for user fields in copy-assignement operator
    End Operator

    Dim As UDT u1, u2

    Redim u1.array(1 To 9)
    For I As Integer = Lbound(u1.array) To Ubound(u1.array)
      u1.array(I) = I
    Next I
     
    u2 = u1
    For I As Integer = Lbound(u2.array) To Ubound(u2.array)
      Print u2.array(I);
    Next I
    Print

    Dim As UDT u3 = u1
    For I As Integer = Lbound(u3.array) To Ubound(u3.array)
      Print u3.array(I);
    Next I
    Print

    Sleep
3)
The elementary dynamic array member cannot be processed as a complex object like a dynamic string member, because there is not implicit assignment (referring to the above example, 'This.array = u.array' is disallowed).
The user must code explicitly the sizing and the copying of the array member.

Example 3:
    Array sizing and copying explicitly set in the user copy-constructor and copy-assignment operator:

    Code: Select all

    #include "crt/string.bi"

    Type UDT
      Dim As Integer array(Any)
      'user fields
      Declare Constructor ()
      Declare Constructor (Byref u As UDT)
      Declare Operator Let (Byref u As UDT)
    End Type

    Constructor UDT ()
      'code for user fields in constructor
    End Constructor

    Constructor UDT (Byref u As UDT)
      'code for user fields in copy-constructor
      if Ubound(u.array) >= Lbound(u.array) Then  '' explicit array sizing and copying
        Redim This.array(Lbound(u.array) To Ubound(u.array))
        memcpy(@This.array(Lbound(This.array)), @u.array(Lbound(u.array)), (Ubound(u.array) - Lbound(u.array) + 1) * Sizeof(@u.array(Lbound(u.array))))
      End If
    End Constructor

    Operator UDT.Let (Byref u As UDT)
      'code for user fields in copy-assignement operator
      if @This <> @u And Ubound(u.array) >= Lbound(u.array) Then  '' explicit array sizing and copying
        Redim This.array(Lbound(u.array) To Ubound(u.array))
        memcpy(@This.array(Lbound(This.array)), @u.array(Lbound(u.array)), (Ubound(u.array) - Lbound(u.array) + 1) * Sizeof(@u.array(Lbound(u.array))))
      End If
    End Operator

    Dim As UDT u1, u2

    Redim u1.array(1 To 9)
    For I As Integer = Lbound(u1.array) To Ubound(u1.array)
      u1.array(I) = I
    Next I
     
    u2 = u1
    For I As Integer = Lbound(u2.array) To Ubound(u2.array)
      Print u2.array(I);
    Next I
    Print

    Dim As UDT u3 = u1
    For I As Integer = Lbound(u3.array) To Ubound(u3.array)
      Print u3.array(I);
    Next I
    Print

    Sleep

    Code: Select all

     1 2 3 4 5 6 7 8 9
     1 2 3 4 5 6 7 8 9
4)
Another elegant possibility is to keep this sizing/copying, automatically coded by the compiler, but by simply calling it explicitly. For this, an obvious solution for the member array is to no longer put it at the level of the Type itself, but rather in another specific Type, but inherited (seen from the outside, it is exactly the same).

Example 4:
    Simpler example using a base Type including the dynamic array member:

    Code: Select all

    Type UDT0
      Dim As Integer array(Any)
    End Type

    Type UDT Extends UDT0
      'user fields
      Declare Constructor ()
      Declare Constructor (Byref u As UDT)
      Declare Operator Let (Byref u As UDT)
    End Type

    Constructor UDT ()
      'code for user fields in constructor
    End Constructor

    Constructor UDT (Byref u As UDT)
      'code for user fields in copy-constructor
      Base(u)  '' inherited array sizing and copying from Base copy-constructor
    End Constructor

    Operator UDT.Let (Byref u As UDT)
      'code for user fields in copy-assignement operator
      Cast(UDT0, This) = u  '' inherited array sizing and copying from Base copy-assignement operator
    End Operator

    Dim As UDT u1, u2

    Redim u1.array(1 To 9)
    For I As Integer = Lbound(u1.array) To Ubound(u1.array)
      u1.array(I) = I
    Next I
     
    u2 = u1
    For I As Integer = Lbound(u2.array) To Ubound(u2.array)
      Print u2.array(I);
    Next I
    Print

    Dim As UDT u3 = u1
    For I As Integer = Lbound(u3.array) To Ubound(u3.array)
      Print u3.array(I);
    Next I
    Print

    Sleep

    Code: Select all

     1 2 3 4 5 6 7 8 9
     1 2 3 4 5 6 7 8 9

See also:
- How and Why to Define Constructors, Assignment-Operators, and Destructor for UDTs in FB
- How to Use the Lbound / Ubound Size information of Array in FB

Return to “Documentation”

Who is online

Users browsing this forum: No registered users and 1 guest