【问题标题】:How to initialize var Record parameter如何初始化 var Record 参数
【发布时间】:2017-06-12 03:05:20
【问题描述】:

我有记录类型:

type
  TIPInfo = record
    IP,
    HostName,
    City,
    Region,
    Country,
    Loc,
    Org: WideString
  end;

返回记录数据的函数:

function GetPublicIPInfo(var IPInfo: TIPInfo): Boolean;
begin
  // initialize
  FillChar(IPInfo, SizeOf(TIPInfo), 0);

  // populate data
  IPInfo.IP := GetVallue('ip');
  IPInfo.HostName := GetVallue('hostname');
  IPInfo.City := GetVallue('city');
  // etc...

  Result := IsOk;   
end;

调用者:

var
  IPInfo: TIPInfo;

if GetPublicIPInfo(IPInfo) then... // use data

通过调用FillChar 来初始化var TIPInfo 是正确的方法还是应该将每个字段都设置为空字符串?调用者应该这样做吗?

另外,在这种情况下使用out参数会更好吗(因为函数没有读取数据)?

【问题讨论】:

  • 当函数不读取参数时,我会使用 out 参数。此外,在这种情况下,很明显函数本身应该初始化它而不是调用者。 FillChar 不会在每种情况下正确初始化像字符串这样的托管类型(尽管在您的情况下确实如此),所以我只会分别初始化每个字段。
  • 在这种情况下,所有记录字段都是WideString,由编译器自动初始化,所以函数根本不需要初始化任何东西。话虽如此,我同意 R.Beiber 的观点,使用out 而不是var 是最好的选择。
  • @RemyLebeau 成员可以具有来自该对象的某些先前使用的任意值。所以他们确实需要被分配。
  • @R.Beiboer,“虽然在你的情况下确实如此”是什么意思?
  • @zig 当所有 WideStrings 都是空字符串时,FillChar 不会造成伤害。如果分配了其中任何一个,FillChar 不会处理字符串。我假设在您的情况下它们是空的,这就是为什么我说“尽管在您的情况下确实如此”。此外,我认为您应该接受 David 的回答,因为他清楚地解释了初始化记录的工作原理以及 out 和 var 的用法。

标签: delphi delphi-7


【解决方案1】:

这里只使用FillChar 是错误的。如果任何WideString 成员不为空,那么您将以这种方式泄漏它们。相反,我建议如下:

Finalize(IPInfo);
FillChar(IPInfo, SizeOf(TIPInfo), 0);

或者另一种方式是将默认记录定义为类型化常量:

const
  DefaultIPInfo: TIPInfo = ();

然后你可以使用简单的赋值:

IPInfo := DefaultIPInfo;

在现代版本的 Delphi 中,您可以改用以下更具可读性的代码:

IPInfo := Default(TIPInfo);

有关此特定主题的更多信息,请参阅以下主题:

请注意,您的代码中的泄漏很难找到,因为WideString 变量被实现为COM BSTR 对象,并分配在COM 堆上。因此,如果您对 Delphi 内存管理器使用内存泄漏检测,则不会检测到泄漏,因为它是从不同的堆中泄漏的。

在您的情况下,因为您的记录是托管类型,并且仅包含托管类型,所以您可以使用 out 参数来获得良好的效果。对于托管类型,out 参数意味着编译器将在调用站点生成代码,在传入之前默认初始化记录。

考虑以下程序:

{$APPTYPE CONSOLE}

type
  TRec = record
    Value: WideString;
  end;

procedure Foo1(var rec: TRec);
begin
end;

procedure Foo2(out rec: TRec);
begin
end;

procedure Main;
var
  rec: TRec;
begin
  rec.Value := 'Foo';
  Foo1(rec);
  Writeln(rec.Value);
  Foo2(rec);
  Writeln(rec.Value);
end;

begin
  Main;
end.

输出是:

如果您的记录包含托管和非托管类型的混合,那么情况就不太好了。

{$APPTYPE CONSOLE}

type
  TRec = record
    Value1: WideString;
    Value2: Integer;
  end;

procedure Foo1(var rec: TRec);
begin
end;

procedure Foo2(out rec: TRec);
begin
end;

procedure Main;
var
  rec: TRec;
begin
  rec.Value1 := 'Foo';
  rec.Value2 := 42;
  Foo1(rec);
  Writeln(rec.Value1);
  Writeln(rec.Value2);
  Foo2(rec);
  Writeln(rec.Value1);
  Writeln(rec.Value2);
end;

begin
  Main;
end.

输出是:

富 42 42

只有托管成员默认为out 参数初始化。所以最好的办法是默认自己初始化变量,即使它是作为out 参数传递的。

更多关于out参数可以在这里找到:What's the difference between "var" and "out" parameters?

【讨论】:

  • 所以你建议总是致电Finalize(IPInfo); FillChar(IPInfo, SizeOf(TIPInfo), 0);?
  • 我的意见是您应该编写代码,以便它能够适应未来的变化。这意味着始终默认初始化记录,即使它恰好只包含托管类型。如果您记录了问题,那么您可以使用out 参数并依靠编译器在调用站点默认初始化。但是,在未来的某一天,您将在记录中添加一个非托管成员,并且在几英里之外的一些功能将会中断。我的看法是out 几乎没用了。
  • 我又要问了:我可以随时调用Finalize(IPInfo); FillChar(IPInfo, SizeOf(TIPInfo), 0); 作为通用 解决方案吗?有没有通用的解决方案?
  • 是的,我说过你可以做到。这在答案和我上面的评论中很清楚。
  • 输入常量的好技巧!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-10
  • 2010-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多