你说:
如果 Result 未定义,这意味着我总是必须通过定义 Result 来启动每个函数,以防以后出现异常。
如果函数引发异常,您担心函数的返回值未定义。但这应该没关系。考虑以下代码:
x := fn();
如果函数体fn
引发异常,x
则不应将其分配给调用站点。从逻辑上讲,上面的单行可以被认为是两行:
- 称呼
fn()
- 将返回值分配给
x
如果在第 1 行引发异常,则第 2 行永远不会发生,x
也不应该被分配给。
因此,如果在分配之前引发了异常,Result
那么这根本不是问题,因为如果函数引发异常,则永远不应使用函数的返回值。
你实际上应该关心的是一个相关的问题。如果您分配给Result
然后引发异常怎么办?您分配的值是否有可能Result
传播到函数之外?可悲的是,答案是肯定的。
对于许多结果类型(例如整数、布尔值等),Result
如果该函数引发异常,您分配给的值不会传播到该函数之外。到目前为止,一切都很好。
但是对于某些结果类型(字符串、动态数组、接口引用、变体等),有一个实现细节使事情变得复杂。返回值作为var
参数传递给函数。事实证明,您可以从函数外部初始化返回值。像这样:
s := 'my string';
s := fn();
当 的主体fn
开始执行时,Result
具有值'my string'
。好像fn
是这样声明的:
procedure fn(var Result: string);
这意味着您可以分配给Result
变量并在调用站点查看修改,即使您的函数随后引发异常。没有干净的方法可以解决它。您可以做的最好的事情是分配给函数中的局部变量,并且仅将 Result 分配为函数的最终行为。
function fn: string;
var
s: string;
begin
s := ...
... blah blah, maybe raise exception
Result := s;
end;
return
这里强烈地感觉到缺乏 C 风格的陈述。
很难准确地说明哪种类型的结果变量容易受到上述问题的影响。最初我认为问题只是影响了托管类型。但 Arnaud 在评论中指出,记录和对象也受到影响。好吧,如果记录或对象是堆栈分配的,那就是真的。如果它是全局变量或堆分配的(例如类的成员),那么编译器会以不同的方式处理它。对于堆分配记录,使用隐式堆栈分配变量返回函数结果。只有当函数返回时,它才会被复制到堆分配的变量中。因此,您在调用站点为函数结果变量分配的值会影响函数本身的语义!
在我看来,这非常清楚地说明了为什么在语言设计中函数返回值具有var
语义而不是语义是一个可怕的错误out
。