代码之家  ›  专栏  ›  技术社区  ›  Z.B.

记录构造函数和字段初始化错误

  •  4
  • Z.B.  · 技术社区  · 6 年前

    Delphi 10.2.3中的以下代码:

    uses
      System.SysUtils;
    
    type
      TRec = record
      strict private
        FName: String;
        FValue: Integer;
      public
        property Name: String read FName;
        property Value: Integer read FValue;
    
        constructor Create(const AName: String);
        function WithValue(const AValue: Integer): TRec;
      end;
    
    constructor TRec.Create(const AName: String);
    begin
      FName := AName;
    end;
    
    function TRec.WithValue(const AValue: Integer): TRec;
    begin
      Result := Self;
      Result.FValue := AValue;
    end;
    
    procedure Main;
    var
      x: TRec;
    begin
      x := TRec.Create('First').WithValue(666);
      x := TRec.Create('Second');
      Writeln('In stack: ', x.Value);
    end;
    
    var
      x: TRec;
    begin
      x := TRec.Create('First').WithValue(666);
      x := TRec.Create('Second');
      Writeln('In global: ', x.Value);
    
      Main;
      Readln;
    end.
    

    生成以下输出:

    In global: 0
    In stack: 666
    

    这是故意的吗?当对数据段中的全局变量进行赋值时,编译器会生成“call@CopyRecord”行,但当使用堆栈中的局部变量时,编译器不会添加这一行。。。

    对于全球:

    Project17.dpr.47: x := TRec.Create('First').WithValue(666);
    0041D56B 8D45E0           lea eax,[ebp-$20]
    0041D56E BA44D64100       mov edx,$0041d644
    0041D573 E87CDAFFFF       call TRec.Create
    0041D578 8D55E0           lea edx,[ebp-$20]
    0041D57B B8C0584200       mov eax,$004258c0
    0041D580 8B0D64AF4100     mov ecx,[$0041af64]
    0041D586 E865B2FEFF       call @CopyRecord
    0041D58B B8C0584200       mov eax,$004258c0
    0041D590 8D4DE8           lea ecx,[ebp-$18]
    0041D593 BA9A020000       mov edx,$0000029a
    0041D598 E877DAFFFF       call TRec.WithValue
    0041D59D 8D55E8           lea edx,[ebp-$18]
    0041D5A0 B8B8584200       mov eax,$004258b8
    0041D5A5 8B0D64AF4100     mov ecx,[$0041af64]
    0041D5AB E840B2FEFF       call @CopyRecord
    Project17.dpr.48: x := TRec.Create('Second');
    0041D5B0 8D45D8           lea eax,[ebp-$28]
    0041D5B3 BA5CD64100       mov edx,$0041d65c
    0041D5B8 E837DAFFFF       call TRec.Create
    0041D5BD 8D55D8           lea edx,[ebp-$28]
    0041D5C0 B8B8584200       mov eax,$004258b8
    0041D5C5 8B0D64AF4100     mov ecx,[$0041af64]
    0041D5CB E820B2FEFF       call @CopyRecord
    

    对于本地:

    Project17.dpr.39: x := TRec.Create('First').WithValue(666);
    0041B074 8D45F0           lea eax,[ebp-$10]
    0041B077 BAF8B04100       mov edx,$0041b0f8
    0041B07C E873FFFFFF       call TRec.Create
    0041B081 8D45F0           lea eax,[ebp-$10]
    0041B084 8D4DF8           lea ecx,[ebp-$08]
    0041B087 BA9A020000       mov edx,$0000029a
    0041B08C E883FFFFFF       call TRec.WithValue
    Project17.dpr.40: x := TRec.Create('Second');
    0041B091 8D45F8           lea eax,[ebp-$08]
    0041B094 BA10B14100       mov edx,$0041b110
    0041B099 E856FFFFFF       call TRec.Create
    Project17.dpr.41: Writeln('In stack: ', x.Value);
    0041B09E A1ACF54100       mov eax,[$0041f5ac]
    0041B0A3 BA2CB14100       mov edx,$0041b12c
    0041B0A8 E87BA8FEFF       call @Write0UString
    

    我应该在记录的每个构造函数中都使用这样的行吗?

    Self := Default(TRec);
    

    因为如果我添加这一行,那么输出是直观的,对于这两种情况都返回0。

    1 回复  |  直到 6 年前
        1
  •  6
  •   David Heffernan    6 年前

    这是故意的吗?

    记录是值类型。当分配此类类型的局部变量时,它们不是默认初始化的。所以,是的,这是按设计的。

    对以下对象执行默认初始化:

    • 全局变量
    • 类实例
    • 托管类型的所有变量

    我应该在记录的每个构造函数中都使用这样的行吗?

    Self := Default(TRec);
    

    是的,如果您希望构造函数初始化记录的每个字段。


    就我个人而言,就可读性而言,我不太喜欢记录构造函数。当我看到:

    foo := TBar.Create(...);
    

    我想 foo 作为类的实例,因此我还希望看到 foo.Free 当实例的生存期结束时。

    我自己喜欢使用静态类方法,总是命名为 New ,以构造新生成的值类型实例。

    我也不是你的粉丝 WithValue 实例方法。我认为强制类的使用者用名称填充一个实例,然后调用 有价值的 在该实例上通过提供值来完成作业。我会这样写:

    type
      TRec = record
      strict private
        FName: String;
        FValue: Integer;
      public
        property Name: String read FName;
        property Value: Integer read FValue;
      public
        class function New(const Name: String; const Value: Integer): TRec; static;
      end;
    
    class function TRec.New(const Name: String; const Value: Integer): TRec;
    begin
      Result.FName := Name;
      Result.FValue := Value;
    end;