4

I work on the standard data class of our software. Here a minimal example of the current state:

  TDataOne = (Width, Length, Height);
  TDataTwo = (Diameter, Weight);

  TDatStruct = class;

  TDatSubStructOne = class(TDatStruct)
    public
      myData : array[TDataOne] of double;
      procedure writeToFile(AFileName : string);
  end;
  TDatSubStructTwo = class(TDatStruct)
    public
      myData : array[TDataTwo] of double;
      procedure writeToFile(AFileName : string);
  end;

In order to reduce code redundancy I would like to conceive a data structure with a common writing function in TDatStruct. The challenges (at least for me as amateur) are:

  1. mydata is of variable size depending on the substruct and bases on different enum types
  2. I still want to access mydata with an enumerated type in the sub structs
  3. I do not want to pass the sub structure data as parameter to the common write function because of the number of different arrays in the real code

The only idea (neglecting point 3) I have is to pass the array data of the sub structure to the common write procedure as open array parameter like:

writeToFile(AFileName : string; writeData : array of double);

Is there any way to combine a dynamic array with an enumerated type or a virtual enumerated array? Or am I completely on the wrong path?

6
  • Always use const in your parameter definitions if at all possible. Commented May 4, 2016 at 17:35
  • Do you possibly also want to read the files? Then you would need to identify which class following doubles in the file represent, no? Commented May 4, 2016 at 20:23
  • @Johan: thanks, i will keep this in mind! Commented May 4, 2016 at 20:45
  • @TomBrunberg: I actually want to read the files again, right. I have something like dataOneStrings : array[TDataOne] of shortstrings storing the used identifiers. Sub structs are either stored in different files OR have unique identifiers. Commented May 4, 2016 at 20:59
  • @TomBrunberg, that problem is easily solved with streaming, If you don't know the types you''l encounter, then write a prefix byte that denotes the type. Commented May 4, 2016 at 21:31

2 Answers 2

4

mydata is of variable size depending on the substruct and bases on different enum types

Delphi has the intrinsic functions Low and High
You don't need to know the size of the array.

I still want to access mydata with an enumerated type in the sub structs

Use the succ and pred intrinsic methods to walk through the enumeration.

I do not want to pass the sub structure data as parameter to the common write function because of the number of different arrays in the real code

All of your arrays look the same to me... They are simply array of double, albeit with a different number of elements.

You can implement writeToFile like so:

procedure writeArrayToFile(const AFileName : string; const writeData : array of double);
var
  i: integer;
begin
  for i:= low(writeData) to High(writeData) do begin
    WriteADoubleToFile(AFilename, writeData[i]);
  end; {for i}
end;

Note the use of const if you don't use this the array will be copied to the stack, causing huge delays with big arrays.

If the array is a data member of your class, then you can simply write the code like so:

procedure TDatStruct.writeToFile(const AFileName : string);
begin
  WriteArrayToFile(FileName, GetMyData^);
end;

Note that because the parent class TDatStruct does not actually have any data inside, you'll need to write a virtual function that will get that data.

type 
  TMyArray = array[0..0] of double;
  PMyArray = ^TMyArray; 

.....
 TDatStruct = class
 protected 
   function GetMyData: PMyArray; virtual; abstract;  
 public
   procedure WriteToFile(const Filename: string); //no need for virtual  
 end;

The open array parameter of the WriteArrayToFile will fix the issue for you.

Note that the array parameters in writeToFile is an "open array parameter", this is will accept both static and dynamic arrays.

If you use different kinds of arrays (double/string etc).
Then use a generic method.

Is there any way to combine a dynamic array with an enumerated type or a virtual enumerated array? Or am I completely on the wrong path?

A dynamic array has a variable number of elements.
You cannot index it using a enumeration directly.
However you can translate the enumerated value into an integer using the Ord intrinsic function.
It also works for enumerations with more than 256 values. The documentation is incorrect, it will return bytes, words or cardinals as needed.

function FixedArrayToDynamicArray(const input: array of double): TArray<double>;  
begin
  //translate a fixed array to a dynamic array.  
  SetLength(Result, High(input)); 
  Move(input[0], Result[0], SizeOf(Double) * High(input));
end;

pre-generics the code becomes:

type 
  TDoubleArray = array of double; 

function FixedArrayToDynamicArray(const input: array of double): TDoubleArray;  
... same from here on. 
Sign up to request clarification or add additional context in comments.

4 Comments

I think the function GetMyData is exactly what I am looking for. I had the problem to get a reference for a variable which is only defined (as enumerated array) in the descendant. As soon as I've tried it successfully with my code I will mark as solved!
When I implement function GetMyData as Result := @myData I have the problem that High(writeData) in function writeArrayToFile returns 0 and I can only access the first element of the array.
@Sid, yes, The result of GetMyData only has one element. Hmm, you'll have to use FixedArrayToDynamicArray or have GetMyData return the number of elements in an out parameter.
The alternative with FixedArrayToDynamicArray works fine for the readFile part. But when it comes to reading the file and writing to the array I would need a reference to the original array. Using TArray = array[0..0] and PArray = ^TArray I managed to access the first element using TArray(pData^)[d], but I get range check exceptions when d<>0. The data arrays of the sub structs have different sizes and I don't want to define a different type for each sub struct. Is there an elegant way to dereference the pointer when I get the length from the GetMydata function?
0

There are many different approaches, it's difficult to say what is best in your situation.

Surely, WriteToFile should be introduced in TDatStruct as abstract:

TDatStruct = class
  public
    procedure WriteToFile(AFileName: string); virtual; abstract;

and descendants should implement it differently:

TDatStructOne = class (TDatStruct)
  ...
  public
    procedure WriteToFile(AFileName: string); override;

in such way, you can have object of class TDatStruct which can actually be TDatStructOne or TDatStructTwo, but code which use it doesn't need to 'know', what it deals with: basic polymorphism.

In such a case you have lot of freedom about data types you use to store information in each class, implementations may vary. The simplest would be:

TDatStructOne = class (TDatStruct)
  public
    Width: Double;
    Length: Double;
    Height: Double;
    procedure WriteToFile(AFileName: string); override;
end;

TDatStructTwo = class (TDatStruct)
  public
    Diameter: Double;
    Weight: Double;
    procedure WriteToFile(AFileName: string); override;
end;

and in WriteToFile each descendants does its job.

So, by your challenges:

  1. you will have data of variable size, not necessarily array of doubles, anything you want (strings, integers, sets etc.)
  2. You've got convenient access to all these fields as long as you have objects of class TDatStructOne or TDatStructTwo (concrete classes)
  3. There is no such thing as common write procedure. Instead, you have common "write interface". You write obj.SaveToFile(FileName) and it will be saved accordingly.

4 Comments

You have not even addressed the enumeration issue, apart from the fact your code does not work, because it does not accept an array as a parameter.
@Johan I've got strong feeling that OP is messing with arrays only because it seems the only way to save various data without if's everywhere, and it will work for a while, but then there will be TDatStructThree which stores not only doubles and this all will break apart. But of course my 'telepathy' works only in 50% of cases, maybe here I'm plain wrong.
I think that an array is actually the right way to solve the problem. Sprinkling lose doubles around does not seem the right solution. And it does not scale. Using arrays makes the solution trivial.
The example here only refers to doubles, but actually the data structures have additional arrays for string variables and even dynamic array variables. I use arrays in order to be able to loop through them when e.g. writing to the file.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.