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
TPersistent
class. (Or, it’s not a direct descendant ofTPersistent
, but no ancestor has overridden theAssign
method.) -
In this case, your class should use the
inherited
keyword (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
Assign
method. -
In this case, your class should always use the
inherited
keyword (to call the ancestorAssign
). In general, callinginherited
in 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
Assign
norAssignTo
are overridden, then calling them will result in an exception. -
Also, note that there is no code in
TPersistent
implementation that automatically copies all the fields (or all the published fields) of the classes. That’s why you need to do that yourself, by overridingAssign
in 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
TApple
instance toTApple.Assign
, it will work and copy all the fields. -
If you pass
TOrange
instance toTApple.Assign
, it will work and only copy the common fields shared by bothTOrange
andTApple
. In other words, the fields defined atTFruit
. -
If you pass
TWerewolf
instance toTApple.Assign
, it will raise an exception (becauseTApple.Assign
will callTFruit.Assign
which will callTPersistent.Assign
which 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.