Weil der backing store ein privates Feld sein muss und nicht die Property eines privaten Feldes.
Interessanterweise geht sowas aber mit einem Record, wenn man beim read wieder ein Feld des Records anspricht. Und mit privat oder nicht hat das auch nichts zu tun.
Offenbar möchte der Compiler das direkt auslösen können. Das ist bei einer Klasseninstanz, die ja erst zur Laufzeit erzeugt wird, aber nicht möglich.
Der Unterschied zwischen der Verwendung eines
record
(oder auch eines
object
) zu einer Klasseninstanz ist, dass bei ersterem das komplette Record Teil des für die Klasse alloziierten Feldbereichs ist. Bei letzterem ist nur der Zeiger auf die Klasseninstanz Teil des Feldbereichs. Das sieht man auch sehr schön, wenn man
TObject.InstanceSize
verwendet:
Delphi-Quellcode:
program tinstancesize;
{$apptype console}
{$ifdef fpc}
{$mode objfpc}
{$endif}
type
TTestRec = record
Foo: LongInt;
Bar: Int64;
Blubb: Boolean;
end;
TTestClass1 = class
Rec: TTestRec;
end;
TTestClass2 = class
Obj: TObject;
end;
begin
Writeln(TTestClass1.ClassName, ': ', TTestClass1.InstanceSize);
Writeln(TTestClass2.ClassName, ': ', TTestClass2.InstanceSize);
end.
Ausgabe:
Code:
TTestClass1: 32
TTestClass2: 8
Der Compiler kann nun also das Felder im Record direkt über einen Offset addressieren, während er bei einer Klasseninstanz eine Methode generieren müsste, um den Zugriff über die
RTTI nicht allzu kompliziert zu gestalten, da man das ja beliebig tief schachteln kann:
Delphi-Quellcode:
program trecprop;
{$apptype console}
{$ifdef fpc}
{$mode objfpc}
{$endif}
type
TTestRec1 = record
Foo: LongInt;
end;
TTestRec2 = record
Bar: TTestRec1;
end;
TTestClass = class
Rec: TTestRec2;
property Foo: LongInt read Rec.Bar.Foo;
end;
begin
end.
Natürlich kann man sich heute schon einen Getter schreiben, aber die Frage ist doch: warum kann der Compiler das denn bitteschön nicht für mich machen?
Der Compiler könnte das durchaus machen, doch beim Zugriff auf einen Klassenzeiger ergibt sich ein Problem, dass sich bei einem Record nicht gibt: was, wenn der Klassenzeiger
Nil
ist? Ich mein, klar, der Compiler könnte entweder die
Access Violation einfach durchlassen oder aber einen speziellen Exceptiontyp auslösen, aber wann rechnet man als Verwender der Klasse mit sowas? Der Sinn von Properties ist, dass man als Entwickler der Klasse sehr leicht die Implementierung der Property ändern kann (zum Beispiel von nem Feld zu nem Getter) ohne, dass es der Verwender großartig bemerkt (natürlich generiert der Compiler dann anderen Code, aber der Quelltext ändert sich nicht). Bei solchen automatisch generierten Gettern käme dann auf einmal ein neuer Exceptiontyp hinzu, mit dem man nicht unbedingt rechnet, vielleicht nicht nur als Benutzer nicht, sondern auch als Entwickler nicht.
Noch eine weitere Anmerkung zum Schluss: wer vor dem Performanceoverhead durch den Getter "Angst" hat, der soll doch einfach Inline verwenden:
Delphi-Quellcode:
program tinlinegetter;
{$ifdef fpc}
{$mode objfpc}
{$endif}
type
TTestClass1 = class
Foo: LongInt;
end;
TTestClass2 = class
Bar: TTestClass1;
function GetFoo: LongInt; inline;
property Foo: LongInt read GetFoo;
end;
function TTestClass2.GetFoo: LongInt;
begin
Result := Bar.Foo;
end;
var
t: TTestClass1;
i: LongInt;
begin
i := t.Foo;
end.
(Hinweis: Kompilat nicht ausführen, da
t
und
Bar
nicht initialisiert sind!)
Zumindest FPC optimiert hier durch das
inline
den gesamten Call weg:
Code:
.section .text
.balign 16,0x90
.globl PASCALMAIN
.type PASCALMAIN,@function
PASCALMAIN:
.globl main
.type main,@function
main:
.Lc6:
# Temps allocated between ebp+0 and ebp+0
# [26] begin
pushl %ebp
.Lc8:
.Lc9:
movl %esp,%ebp
.Lc10:
call FPC_INITIALIZEUNITS
# [27] i := t.Foo;
movl U_P$TINLINEGETTER_T,%eax
movl 4(%eax),%eax
movl 4(%eax),%eax
movl %eax,U_P$TINLINEGETTER_I
# [28] end.
call FPC_DO_EXIT
leave
ret
.Lc7:
.Le1:
.size main, .Le1 - main
Im Vergleich dazu der generierte Code ohne
inline
Code:
.section .text
.balign 16,0x90
.globl PASCALMAIN
.type PASCALMAIN,@function
PASCALMAIN:
.globl main
.type main,@function
main:
.Lc6:
# Temps allocated between ebp+0 and ebp+0
# [26] begin
pushl %ebp
.Lc8:
.Lc9:
movl %esp,%ebp
.Lc10:
call FPC_INITIALIZEUNITS
# [27] i := t.Foo;
movl U_P$TINLINEGETTER_T,%eax
call P$TINLINEGETTER_TTESTCLASS2_$__GETFOO$$LONGINT
movl %eax,U_P$TINLINEGETTER_I
# [28] end.
call FPC_DO_EXIT
leave
ret
.Lc7:
.Le1:
.size main, .Le1 - main
Gruß,
Sven