Interfaces e records
Interfaces com contagem de referências, records com construtores e operadores, records gerenciados e a parte variante.
Interfaces#
Uma interface descreve um contrato; várias classes o implementam, e o código usa a interface sem saber qual é a classe.
program Interfaces;
type
{$config InterfaceExtendedSyntax on}
IShape = interface(IInterface)
function Area: Int32; virtual;
function Name: UString; virtual;
end interface;
TSquare = class(TObject, IShape)
private
FSide: Int32;
public
constructor Create(const ASide: Int32);
function Area: Int32; => Exit(FSide * FSide);
function Name: UString; => Exit('quadrado');
end class;
TRectangle = class(TObject, IShape)
private
FWidth, FHeight: Int32;
public
constructor Create(const AWidth, AHeight: Int32);
function Area: Int32; => Exit(FWidth * FHeight);
function Name: UString; => Exit('retângulo');
end class;
constructor TSquare.Create(const ASide: Int32);
begin
FSide := ASide;
end constructor;
constructor TRectangle.Create(const AWidth, AHeight: Int32);
begin
FWidth := AWidth;
FHeight := AHeight;
end constructor;
procedure Show(const AShape: IShape);
begin
WriteLn(AShape.Name, ': área ', AShape.Area);
end procedure;
begin
// a referência de interface mantém o objeto vivo (contagem de referências)
Show(TSquare.Create(4));
Show(TRectangle.Create(3, 5));
end.quadrado: área 16
retângulo: área 15- Toda interface herda de
IInterface(fora do móduloSystem). O despacho é pela ITable da classe. - Uma referência de interface mantém o objeto vivo (contagem de referências):
Show(TSquare.Create(4))não vaza nem precisa deFree. - Uma interface pode ser declarada adiantada (
IWriter = interface;), com o corpo depois: é o que resolve duas interfaces que se citam. Uma adiantada que nunca ganha corpo é erro, porque quem a citou receberia uma referência que compila e não tem método nenhum. - Interfaces de COM têm a ABI própria (
safecall,stdcall), e a RTL as usa para o Windows (IStream,IUnknown…).
Records#
Um record é um valor: atribuir copia. Pode ter métodos, construtores e operadores, e herdar de outro record.
program Records;
type
TPoint = record
X, Y: Int32;
public
constructor Create(const AX, AY: Int32);
class operator Add(const A, B: TPoint): TPoint;
=> Exit(TPoint.Create(A.X + B.X, A.Y + B.Y));
class operator Equal(const A, B: TPoint): Bool8;
=> Exit((A.X = B.X) and (A.Y = B.Y));
end record;
// record gerenciado: Initialize quando passa a existir, Finalize quando deixa de existir
TGuard = managed record
Id: Int32;
class operator Initialize(out Dest: TGuard); => Dest.Id := 7;
class operator Finalize(var Dest: TGuard); => WriteLn(' Finalize, Id=', Dest.Id);
end record;
constructor TPoint.Create(const AX, AY: Int32);
begin
X := AX;
Y := AY;
end constructor;
procedure Scope;
var
G: TGuard;
begin
WriteLn(' Initialize deu Id=', G.Id);
G.Id := 42;
end procedure;
var
A, B, C: TPoint;
begin
A := TPoint.Create(1, 2); // constrói direto no destino: sem alocação
B := TPoint.Create(10, 20);
C := A + B; // o operador Add do record
WriteLn('C = (', C.X, ', ', C.Y, ')');
WriteLn('C = A + B? ', C = TPoint.Create(11, 22));
WriteLn('entrando no escopo:');
Scope;
WriteLn('saiu do escopo');
end.C = (11, 22)
C = A + B? TRUE
entrando no escopo:
Initialize deu Id=7
Finalize, Id=42
saiu do escopotype
TPoint = record
X, Y: Int32;
public
constructor Create(const AX, AY: Int32);
class operator Add(const A, B: TPoint): TPoint;
end record;TPoint.Create(…)constrói no destino: nenhuma alocação. Um construtor pode delegar para outra sobrecarga.- Um record pode herdar de outro (
TDerived = record(TBase)): os campos vêm depois doSizeOfdo ancestral, e a passagem é por referência, não por valor. packed recordremove o preenchimento, eend align(N)fixa um alinhamento mínimo (um piso, não um teto).
Operadores#
Cada operador é um class operator, um método de classe static: não há Self, os dois operandos são os parâmetros, e não precisam ser do mesmo tipo.
| Grupo | Operadores |
|---|---|
| De valor | Add Subtract Multiply Divide Equal NotEqual LessThan LessThanOrEqual GreaterThan GreaterThanOrEqual … |
| De destino | Assign Increment Decrement — escrevem no destino em vez de devolver valor; o primeiro operando é var |
| De vida | Initialize Finalize |
| Conversão | Implicit Explicit (a busca exata vence a frouxa) |
| Outros | Default |
Os operadores do ancestral valem para o descendente.
Records gerenciados#
Um managed record tem tempo de vida próprio: Initialize quando a variável passa a existir e Finalize quando deixa de existir (no fim do escopo, de um vetor, de um campo, ou do programa, se for global). Um record comum que contém um campo gerenciado passa a ser gerenciado por tabela, e a inicialização e a finalização descem até ele. É assim que Variant, OleVariant e os anuláveis (?T) são feitos.
Parte variante#
Os campos de uma parte variante se sobrepõem, como uma union do C:
program RecordVariant;
type
PPair^, TPair = record // o ponteiro declarado junto do tipo
public
Tag: Int32;
case Int32 of // parte variante: os campos se sobrepõem
0: (AsInt: Int32);
1: (Bytes: array[0..3] of UInt8);
end case;
end record;
var
R: TPair;
P: PPair;
begin
R.Tag := 1;
R.AsInt := $01020304;
WriteLn('bytes: ', R.Bytes[0], ' ', R.Bytes[3]); // x86 é little-endian
WriteLn('SizeOf(TPair) = ', SizeOf(TPair));
P := @R;
WriteLn('P^.Tag = ', P^.Tag);
end.bytes: 4 1
SizeOf(TPair) = 8
P^.Tag = 1A parte variante é fechada com end case; (o qualificador é conferido pelo compilador). Um rótulo de case pode ser uma expressão constante, e é a forma pela qual as constantes de GUID são escritas. O tipo ponteiro pode ser declarado junto do record, com PPair^, TPair = record.