在一系列方法中用作输出和(非参考)输入的值
Value used as both output and (non-reference) input in a chain of methods
考虑以下方法链接的最小示例,其中浮点变量由早期方法设置(使用 out
参数)然后传递(使用 const
参数)到链中的后一个方法:
program ChainedConundrum;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
ValueType = Double;
TRec = record
function GetValue(out AOutput: ValueType): TRec;
procedure ShowValue(const AInput: ValueType);
end;
function TRec.GetValue(out AOutput: ValueType): TRec;
begin
AOutput := 394;
Result := Self;
end;
procedure TRec.ShowValue(const AInput: ValueType);
begin
Writeln(AInput);
end;
var
R: TRec;
Value: ValueType = 713;
begin
R.GetValue(Value).ShowValue(Value);
Readln;
end.
我最初希望它打印浮点数 394
(以某种格式),但它没有(必须);当我使用 Delphi 10.3.2 的 32 位编译器构建程序时,程序打印 713
。使用调试器单步执行程序确认 Value
的初始 GetValue
值已传递给 ShowValue
.
但是,如果我使用 64 位编译器构建它,则会打印 394
。同样,如果我将 ValueType
从 Double
更改为 Int32
,我会在两个版本中得到 394
。 Int64
在 64 位中产生 394
,在 32 位中产生 713
。字符串产生更新后的值。 Classes 就像记录一样工作。 Class 方法,但是,与实例方法相反,总是给我更新的值。当然,放弃方法链接 (R.GetValue(Value); R.ShowValue(Value)
) 也是一样的。
毫不奇怪,如果我将 ShowValue
的 AInput
参数从 const
(或未修饰的值)参数更改为 var
参数,我总是得到更新值。
我的结论是
- 不允许在像这样的方法链中设置和传递变量,或者
- 编译器中存在错误。
我的问题是:它是哪个?如果不允许,文档在哪里说明这一点?到目前为止,我还没有找到相关的段落。 (在 WWW 上,短语 "sequence point" 似乎很少出现在短语 "Delphi" 附近。)
在这里或其他地方评论过这个问题的每个人都同意这“感觉像”或“显然”是一个编译器错误。
我在 Embarcadero Jira 创建了问题 RSP-29733。
转向可能的解决方法,请注意问题似乎是编译器使用了变量的旧 value。因此,当 value 更改接近变量的使用时,就会出现问题。
但是,变量的地址没有改变,所以如果你通过引用而不是值传递变量,问题就会消失。一种方法是在第二次传递值时使用 var
参数,即使您不需要它,甚至在语义上不需要它。
因此,更自然的方法似乎是使用 const [Ref]
参数:
procedure ShowValue(const [Ref] AInput: ValueType);
这与未修饰的 const
参数具有相同的语义,但强制编译器通过引用传递变量,从而避免了错误。
考虑以下方法链接的最小示例,其中浮点变量由早期方法设置(使用 out
参数)然后传递(使用 const
参数)到链中的后一个方法:
program ChainedConundrum;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
ValueType = Double;
TRec = record
function GetValue(out AOutput: ValueType): TRec;
procedure ShowValue(const AInput: ValueType);
end;
function TRec.GetValue(out AOutput: ValueType): TRec;
begin
AOutput := 394;
Result := Self;
end;
procedure TRec.ShowValue(const AInput: ValueType);
begin
Writeln(AInput);
end;
var
R: TRec;
Value: ValueType = 713;
begin
R.GetValue(Value).ShowValue(Value);
Readln;
end.
我最初希望它打印浮点数 394
(以某种格式),但它没有(必须);当我使用 Delphi 10.3.2 的 32 位编译器构建程序时,程序打印 713
。使用调试器单步执行程序确认 Value
的初始 GetValue
值已传递给 ShowValue
.
但是,如果我使用 64 位编译器构建它,则会打印 394
。同样,如果我将 ValueType
从 Double
更改为 Int32
,我会在两个版本中得到 394
。 Int64
在 64 位中产生 394
,在 32 位中产生 713
。字符串产生更新后的值。 Classes 就像记录一样工作。 Class 方法,但是,与实例方法相反,总是给我更新的值。当然,放弃方法链接 (R.GetValue(Value); R.ShowValue(Value)
) 也是一样的。
毫不奇怪,如果我将 ShowValue
的 AInput
参数从 const
(或未修饰的值)参数更改为 var
参数,我总是得到更新值。
我的结论是
- 不允许在像这样的方法链中设置和传递变量,或者
- 编译器中存在错误。
我的问题是:它是哪个?如果不允许,文档在哪里说明这一点?到目前为止,我还没有找到相关的段落。 (在 WWW 上,短语 "sequence point" 似乎很少出现在短语 "Delphi" 附近。)
在这里或其他地方评论过这个问题的每个人都同意这“感觉像”或“显然”是一个编译器错误。
我在 Embarcadero Jira 创建了问题 RSP-29733。
转向可能的解决方法,请注意问题似乎是编译器使用了变量的旧 value。因此,当 value 更改接近变量的使用时,就会出现问题。
但是,变量的地址没有改变,所以如果你通过引用而不是值传递变量,问题就会消失。一种方法是在第二次传递值时使用 var
参数,即使您不需要它,甚至在语义上不需要它。
因此,更自然的方法似乎是使用 const [Ref]
参数:
procedure ShowValue(const [Ref] AInput: ValueType);
这与未修饰的 const
参数具有相同的语义,但强制编译器通过引用传递变量,从而避免了错误。