6.1. Input/output using streams
Modern programs should use TStream class and it’s many descendants to do input / output. It has many useful descendants, like TFileStream, TMemoryStream, TStringStream.
{$mode delphi}
uses SysUtils, Classes;
var
S: TStream;
InputInt, OutputInt: Integer;
begin
InputInt := 666;
S := TFileStream.Create('my_binary_file.data', fmCreate);
try
S.WriteBuffer(InputInt, SizeOf(InputInt));
finally FreeAndNil(S) end;
S := TFileStream.Create('my_binary_file.data', fmOpenRead);
try
S.ReadBuffer(OutputInt, SizeOf(OutputInt));
finally FreeAndNil(S) end;
Writeln('Read from file got integer: ', OutputInt);
end.In the Castle Game Engine: You should use the Download method to create a stream that operates of resources (which includes files, data downloaded from URLs and Android assets). Moreover, to open the resource inside your game data (typically in data subdirectory) use the ApplicationData function.
EnableNetwork := true;
S := Download('https://castle-engine.io/latest.zip');S := Download('file:///home/michalis/my_binary_file.data');S := Download(ApplicationData('gui/my_image.png'));To read text files, we advice using the TTextReader class. It provides a line-oriented API, and wraps a TStream inside. The TTextReader constructor can take a ready URL, or you can pass there your custom TStream source.
Text := TTextReader.Create(ApplicationData('my_data.txt'));
while not Text.Eof do
WritelnLog('NextLine', Text.ReadLine);6.2. Containers (lists, dictionaries) using generics
The language and run-time library offer various flexible containers. There are a number of non-generic classes (like TList and TObjectList from the Contnrs unit), there are also dynamic arrays (array of TMyType). But to get the most flexibility and type-safety, I advice using generic containers for most of your needs.
The generic containers give you a lot of helpful methods to add, remove, iterate, search, sort… The compiler also knows (and checks) that the container holds only items of the appropriate type.
There are three libraries providing generics containers in FPC now:
We advice using the FGL unit (if you need to work with the stable FPC 3.0.x or even older FPC 2.6.x), or the Generics.Collections unit (only available since FPC 3.1.1, but on the other hand compatible with Delphi). They both offer lists and dictionaries with naming consistent with other parts of the standard library (like the non-generic containers from the Contnrs unit).
In the Castle Game Engine: We include a local copy of Generics.Collections even for FPC 3.0.x. We use the Generics.Collections throughout the engine, and advice you to use Generics.Collections too!
Most important classes from the Generics.Collections unit are:
- TList
-
A generic list of types.
- TObjectList
-
A generic list of object instances. It can "own" children, which means that it will free them automatically.
- TDictionary
-
A generic dictionary.
- TObjectDictionary
-
A generic dictionary, that can "own" the keys and/or values.
Here’s how to you use a simple generic TObjectList:
{$mode delphi}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleList = TObjectList<TApple>;
var
A: TApple;
Apples: TAppleList;
begin
Apples := TAppleList.Create(true);
try
A := TApple.Create;
A.Name := 'my apple';
Apples.Add(A);
A := TApple.Create;
A.Name := 'another apple';
Apples.Add(A);
Writeln('Count: ', Apples.Count);
Writeln(Apples[0].Name);
Writeln(Apples[1].Name);
finally FreeAndNil(Apples) end;
end.Note that some operations require comparing two items, like sorting and searching (e.g. by Sort and IndexOf methods). The Generics.Collections containers use for this a comparer. The default comparer is reasonable for all types, even for records (in which case it compares memory contents, which is a reasonable default at least for searching using IndexOf).
When sorting the list you can provide a custom comparer as a parameter. The comparer is a class implementing the IComparer interface. In practice, you usually define the appropriate callback, and use TComparer<T>.Construct method to wrap this callback into an IComparer instance. An example of doing this is below:
{$mode delphi}
uses SysUtils, Generics.Defaults, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleList = TObjectList<TApple>;
function CompareApples(constref Left, Right: TApple): Integer;
begin
Result := AnsiCompareStr(Left.Name, Right.Name);
end;
type
TAppleComparer = TComparer<TApple>;
var
A: TApple;
L: TAppleList;
begin
L := TAppleList.Create(true);
try
A := TApple.Create;
A.Name := '11';
L.Add(A);
A := TApple.Create;
A.Name := '33';
L.Add(A);
A := TApple.Create;
A.Name := '22';
L.Add(A);
L.Sort(TAppleComparer.Construct(@CompareApples));
Writeln('Count: ', L.Count);
Writeln(L[0].Name);
Writeln(L[1].Name);
Writeln(L[2].Name);
finally FreeAndNil(L) end;
end.The TDictionary class implements a dictionary, also known as a map (key → value), also known as an associative array. It’s API is a bit similar to the C# TDictionary class. It has useful iterators for keys, values, and pairs of key→value.
An example code using a dictionary:
{$mode delphi}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleDictionary = TDictionary<string, TApple>;
var
Apples: TAppleDictionary;
A, FoundA: TApple;
ApplePair: TAppleDictionary.TDictionaryPair;
AppleKey: string;
begin
Apples := TAppleDictionary.Create;
try
A := TApple.Create;
A.Name := 'my apple';
Apples.AddOrSetValue('apple key 1', A);
if Apples.TryGetValue('apple key 1', FoundA) then
Writeln('Found apple under key "apple key 1" with name: ' +
FoundA.Name);
for AppleKey in Apples.Keys do
Writeln('Found apple key: ' + AppleKey);
for A in Apples.Values do
Writeln('Found apple value: ' + A.Name);
for ApplePair in Apples do
Writeln('Found apple key->value: ' +
ApplePair.Key + '->' + ApplePair.Value.Name);
Apples.Remove('apple key 1');
A.Free;
finally FreeAndNil(Apples) end;
end.The TObjectDictionary can additionally own the dictionary keys and/or values, which means that they will be automatically freed. Be careful to only own keys and/or values if they are object instances. If you set to "owned" some other type, like an Integer (for example, if your keys are Integer, and you include doOwnsKeys), you will get a nasty crash when the code executes.
An example code using the TObjectDictionary is below. Compile this example with memory leak detection, like fpc -gl -gh generics_object_dictionary.lpr, to see that everything is freed when program exits.
{$mode delphi}
uses SysUtils, Generics.Collections;
type
TApple = class
Name: string;
end;
TAppleDictionary = TObjectDictionary<string, TApple>;
var
Apples: TAppleDictionary;
A: TApple;
ApplePair: TAppleDictionary.TDictionaryPair;
begin
Apples := TAppleDictionary.Create([doOwnsValues]);
try
A := TApple.Create;
A.Name := 'my apple';
Apples.AddOrSetValue('apple key 1', A);
for ApplePair in Apples do
Writeln('Found apple key->value: ' +
ApplePair.Key + '->' + ApplePair.Value.Name);
Apples.Remove('apple key 1');
finally FreeAndNil(Apples) end;
end.If you prefer using the FGL unit instead of Generics.Collections, here is a short overview of the most important classes from the FGL unit:
- TFPGList
-
A generic list of types.
- TFPGObjectList
-
A generic list of object instances. It can "own" children.
- TFPGMap
-
A generic dictionary.
In FGL unit, the TFPGList can be only used for types for which the equality operator (=) is defined. For TFPGMap the "greater than" (>) and "less than" (<) operators must be defined for the key type. If you want to use these lists with types that don’t have built-in comparison operators (e.g. with records), you have to overload their operators as shown in the Operator overloading.
In the Castle Game Engine we include a unit CastleGenericLists that adds TGenericStructList and TGenericStructMap classes. They are similar to TFPGList and TFPGMap, but they do not require a definition of the comparison operators for the appropriate type (instead, they compare memory contents, which is often appropriate for records or method pointers). But the CastleGenericLists unit is deprecated since the engine version 6.3, as we advice using Generics.Collections instead.
If you want to know more about the generics, see Generics.
6.3. Cloning: TPersistent.Assign
Copying the class instances by a simple assignment operator copies the reference.
var
X, Y: TMyObject;
begin
X := TMyObject.Create;
Y := X;
Y.MyField := 123;
FreeAndNil(X);
end;To copy the class instance contents, the standard approach is to derive your class from TPersistent, and override it’s Assign method. Once it’s implemented properly in TMyObject, you use it like this:
var
X, Y: TMyObject;
begin
X := TMyObject.Create;
Y := TMyObject.Create;
Y.Assign(X);
Y.MyField := 123;
FreeAndNil(X);
FreeAndNil(Y);
end;To make it work, you need to implement the Assign method to actually copy the fields you want. You should carefully implement the Assign method, to copy from a class that may be a descendant of the current class.
{$mode delphi}
uses SysUtils, Classes;
type
TMyClass = class(TPersistent)
public
MyInt: Integer;
procedure Assign(Source: TPersistent); override;
end;
TMyClassDescendant = class(TMyClass)
public
MyString: string;
procedure Assign(Source: TPersistent); override;
end;
procedure TMyClass.Assign(Source: TPersistent);
var
SourceMyClass: TMyClass;
begin
if Source is TMyClass then
begin
SourceMyClass := TMyClass(Source);
MyInt := SourceMyClass.MyInt;
end else
inherited Assign(Source);
end;
procedure TMyClassDescendant.Assign(Source: TPersistent);
var
SourceMyClassDescendant: TMyClassDescendant;
begin
if Source is TMyClassDescendant then
begin
SourceMyClassDescendant := TMyClassDescendant(Source);
MyString := SourceMyClassDescendant.MyString;
end;
inherited Assign(Source);
end;
var
C1, C2: TMyClass;
CD1, CD2: TMyClassDescendant;
begin
C1 := TMyClass.Create;
C2 := TMyClass.Create;
try
C1.MyInt := 666;
C2.Assign(C1);
Writeln('C2 state: ', C2.MyInt);
finally
FreeAndNil(C1);
FreeAndNil(C2);
end;
CD1 := TMyClassDescendant.Create;
CD2 := TMyClassDescendant.Create;
try
CD1.MyInt := 44;
CD1.MyString := 'blah';
CD2.Assign(CD1);
Writeln('CD2 state: ', CD2.MyInt, ' ', CD2.MyString);
finally
FreeAndNil(CD1);
FreeAndNil(CD2);
end;
end.Sometimes it’s more comfortable to alternatively override the AssignTo method in the source class, instead of overriding the Assign method in the destination class.
Be careful when you call inherited in the overridden Assign implementation. There are two situations:
- Your class is a direct descendant of the
TPersistentclass. (Or, it’s not a direct descendant ofTPersistent, but no ancestor has overridden theAssignmethod.) -
In this case, your class should use the
inheritedkeyword (to call theTPersistent.Assign) only if you cannot handle the assignment in your code. - Your class descends from some class that has already overridden the
Assignmethod. -
In this case, your class should always use the
inheritedkeyword (to call the ancestorAssign). In general, callinginheritedin overridden methods is usually a good idea.
To understand the need when to call (or not to call) inherited from the Assign implementation, and how it relates to the AssignTo method, it’s best to look at the TPersistent.Assign and TPersistent.AssignTo implementations:
procedure TPersistent.Assign(Source: TPersistent);
begin
if Source <> nil then
Source.AssignTo(Self)
else
raise EConvertError...
end;
procedure TPersistent.AssignTo(Destination: TPersistent);
begin
raise EConvertError...
end;|
Note |
This is not the exact implementation of TPersistent, I simplified it to hide the boring details about how the exception message is build. |
The conclusions you can get from the above are:
-
If neither
AssignnorAssignToare overridden, then calling them will result in an exception. -
Also, note that there is no code in
TPersistentimplementation that automatically copies all the fields (or all the published fields) of the classes. That’s why you need to do that yourself, by overridingAssignin all the classes. You can use RTTI (runtime type information) for that, but for simple cases you will probably just list the fields to be copied manually.
When you have a class like TApple, your TApple.Assign implementation usually deals with copying fields that are specific to the TApple class (not to the TApple ancestor, like TFruit). So, the TApple.Assign implementation usually checks whether Source is TApple at the beginning, before copying apple-related fields. Then, it calls inherited to allow TFruit to handle the rest of the fields.
Assuming that you implemented TFruit.Assign and TApple.Assign following the standard pattern (as shown in the example above), the effect is like this:
-
If you pass
TAppleinstance toTApple.Assign, it will work and copy all the fields. -
If you pass
TOrangeinstance toTApple.Assign, it will work and only copy the common fields shared by bothTOrangeandTApple. In other words, the fields defined atTFruit. -
If you pass
TWerewolfinstance toTApple.Assign, it will raise an exception (becauseTApple.Assignwill callTFruit.Assignwhich will callTPersistent.Assignwhich raises an exception).
|
Note |
Remember that when descending from TPersistent, the default visibility specifier is published, to allow streaming of TPersistent descendants. Not all field and property types are allowed in the published section. If you get errors related to it, and you don’t care about streaming, just change the visibility to public. See the Visibility specifiers. |
from Hacker News https://ift.tt/2NYLTdG
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.