2011-11-28 37 views
3

我可以依靠记录中的接口字段总是初始化为nil的事实吗?记录中的接口字段

TMyRec = record 
    FGuard : IInterface; 
    FObject : TObject; 
    procedure CheckCreated; 
end; 

这将让我写:

procedure TMyCheck.CheckCreated; 
begin 
if (FGuard = nil) then 
    begin 
    FObject := TObject.Create; 
    FGuard := TGuard.Create (FObject); 
    end; 
end; 

(自动生命周期管理)

我知道接口字段被初始化为nil,但包含在记录时是也是如此?

+0

请问下降者请解释她或他的推理吗? – jpfollenius

回答

9

是的,你可以依靠那个。

所有引用计数变量:

  • 字符串;
  • 动态数组;
  • 变体;
  • 接口;
  • 嵌套记录包含这些类型的变量。

被初始化为nil时被分配一个record,如果你使用New或动态数组 - 在堆栈上即使是局部的。当然,如果您使用普通的GetMem或使用指针,则必须自行初始化(例如,使用FillChar)。

如果你是好奇,有一个System.pas下列程序的隐藏调用:

procedure _InitializeRecord(p: Pointer; typeInfo: Pointer); 

这将填补所有的引用计数内存变量为0,但不会设置其他record的成员。实际上,在一个class实例中,整个字段存储器初始化为0,包括所有成员 - 对于record,初始化仅用于引用计数类型。

请注意,在某些情况下,如果使用object类型而不是record - at least under Delphi 2009-2010,我发现此初始化过程未正确生成。所以如果你的代码有一些object类型声明,你最好切换到record(和松散继承),或者明确地调用FillChar

如果您好奇,这里是我在asm中编写的优化版本 - 可在our enhanced RTL中获得。

procedure _InitializeRecord(p: Pointer; typeInfo: Pointer); 
// this procedure is called at most object creation -> optimization rocks here! 
asm 
     { -> EAX pointer to record to be initialized } 
     {  EDX pointer to type info    } 
     MOVZX ECX,[EDX+1]     { type name length } 
     PUSH EBX 
     PUSH ESI 
     PUSH EDI 
     MOV  EBX,EAX      // PIC safe. See comment above 
     LEA  ESI,[EDX+ECX+2+8]   { address of destructable fields } 
     MOV  EDI,[EDX+ECX+2+4]   { number of destructable fields } 
@@loop: 
     mov edx,[esi] // type info 
     mov eax,[esi+4] 
     mov edx,[edx] 
     add esi,8 
     add eax,ebx  // data to be initialized 
     movzx ecx,[edx] // data type 
     cmp ecx,tkLString 
     je @@LString 
     jb @@err 
     cmp ecx,tkDynArray 
     je @@DynArray 
     ja @@err 
     jmp dword ptr [ecx*[email protected]@Tab-tkWString*4] 
     nop; nop; nop // align @@Tab 
@@Tab: dd @@WString,@@Variant,@@Array,@@Record 
     dd @@Interface,@@err 
@@LString: 
@@WString: 
@@Interface: 
@@DynArray: // zero 4 bytes in EAX 
     dec edi 
     mov dword ptr [eax],0 
     jg @@loop 
     POP  EDI 
     POP  ESI 
     POP  EBX 
     RET 
@@Variant: // zero 16 bytes in EAX 
     xor ecx,ecx 
     dec edi 
     mov [eax],ecx 
     mov [eax+4],ecx 
     mov [eax+8],ecx 
     mov [eax+12],ecx 
     jg @@loop 
     jmp @@exit 
@@err: 
     MOV  AL,reInvalidPtr 
     POP  EDI 
     POP  ESI 
     POP  EBX 
     JMP  Error 
@@Array: 
@@Record: // rarely called in practice 
     mov ecx,1 
     call _InitializeArray 
     dec edi 
     jg @@loop 
@@exit: 
     POP  EDI 
     POP  ESI 
     POP  EBX 
end; 
+0

+1非常感谢!任何想法为什么决定不使用'FillChar'来简单地初始化整个记录,这会为我们提供其他数据类型的初始值? – jpfollenius

+0

@Smasher因为这可能会迫使每个人支付运行时成本,当可能不是每个人都想零初始化时。另外,如果您动态分配记录,则需要使用New/Dispose来获取此行为。 GetMem/Free会失败,因为它们缺少类型信息。 –

+0

@David:您确定运行时成本(一次调用'ZeroMemory')是否会高于有选择地初始化托管类型的代码? – jpfollenius