加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

delphi – 为什么我不能将我的函数引用分配给一个匹配的变量? E

发布时间:2020-12-15 06:10:40 所属栏目:大数据 来源:网络整理
导读:我正在尝试构建一个自定义比较器,它允许将比较功能分配给内部字段.为了简化比较器的创建,我尝试添加一个类构造函数类函数Construct来初始化比较器. 现在,如果我尝试编译以下示例,编译器将显示 [dcc32 Fehler] ConsoleDemo1.dpr(37): E2555 Symbol ‘Result’
我正在尝试构建一个自定义比较器,它允许将比较功能分配给内部字段.为了简化比较器的创建,我尝试添加一个类构造函数类函数Construct来初始化比较器.

现在,如果我尝试编译以下示例,编译器将显示

[dcc32 Fehler] ConsoleDemo1.dpr(37): E2555 Symbol ‘Result’ cannot be tracked

我有以下示例代码:

program ConsoleDemo1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  Generics.Collections,Generics.Defaults,System.SysUtils;

type

  TConstFunc<T1,T2,TResult> = reference to function(const Arg1: T1; const Arg2: T2): TResult;

  TDemo = class(TComparer<string>)
  private
    FVar: TConstFunc<string,string,Integer>;
    function CompareInternal(const L,R: string): Integer;
  public
    class function Construct(): TDemo;
    function Compare(const L,R: string): Integer; override;
  end;

function TDemo.Compare(const L,R: string): Integer;
begin
  Result := FVar(L,R);
end;

function TDemo.CompareInternal(const L,R: string): Integer;
begin
  Result := AnsiCompareStr(L,R);
end;

class function TDemo.Construct: TDemo;
begin
  Result := TDemo.Create();
  Result.FVar := Result.CompareInternal;
end;

end.

解决方法

我不认为这是一个bug.最重要的是,您将TConstFunc定义为匿名方法类型.这些被管理,引用计数,非常特殊的类型与常规对象方法截然不同.通过编译器魔术,它们通常是分配兼容的,但有几个重要的注意事项.考虑更简洁:
program Project1;

{$APPTYPE CONSOLE}

type
  TFoo = reference to procedure;

  TDemo = class
  private
    FFoo : TFoo;
    procedure Foo;
  public
    class function Construct(): TDemo;
  end;

procedure TDemo.Foo;
begin
  WriteLn('foo');
end;

class function TDemo.Construct: TDemo;
begin
  result := TDemo.Create();
  result.FFoo := result.foo;
end;

end.

这也产生相同的编译器错误(E2555).因为成员方法是一个object(object method)类型的过程,而是将它分配给一个对过程(匿名方法)类型的引用,这相当于(我怀疑编译器正在将其扩展为):

class function TDemo.Construct: TDemo;
begin
  result := TDemo.Create();
  result.FFoo := procedure
                 begin
                   result.foo;
                 end;
end;

编译器不能直接分配方法引用(因为它们是不同类型的),因此(我猜想)必须将它包装成一个隐式需要捕获结果变量的匿名方法.函数返回值不能被匿名方法捕获,但只有局部变量可以.

在你的情况下(或者,实际上,对于任何函数类型),由于匿名包装器隐藏了结果变量,所以不能表达等价物,但我们可以想象在理论上是相同的:

class function TDemo.Construct: TDemo;
begin
  Result := TDemo.Create();
  Result.FVar := function(const L,R : string) : integer
                 begin
                   result := result.CompareInternal(L,R);  // ** can't do this
                 end;
end;

正如David所说,引入一个局部变量(可以被捕获)是一个正确的解决方案.或者,如果您不需要TConstFunc类型为匿名,则可以简单地将其声明为常规对象方法:

TConstFunc<T1,TResult> = function(const Arg1: T1; const Arg2: T2): TResult of object;

尝试捕获结果的另一个示例将失败:

program Project1;

{$APPTYPE CONSOLE}

type
  TBar = reference to procedure;
  TDemo = class
  private
    FFoo : Integer;
    FBar : TBar;
  public
    class function Construct(): TDemo;
  end;

class function TDemo.Construct: TDemo;
begin
  result := TDemo.Create();
  result.FFoo := 1;
  result.FBar := procedure
                 begin
                   WriteLn(result.FFoo);
                 end;
end;

end.

这个不起作用的根本原因是因为一个方法的返回值实际上是一个var参数,匿名闭包捕获变量而不是值.这是一个关键点.同样,这也是不允许的:

program Project1;

{$APPTYPE CONSOLE}

type
  TFoo = reference to procedure;

  TDemo = class
  private
    FFoo : TFoo;
    procedure Bar(var x : integer);
  end;

procedure TDemo.Bar(var x: Integer);
begin
  FFoo := procedure
          begin
            WriteLn(x);
          end;
end;

begin
end.

[dcc32 Error] Project1.dpr(18): E2555 Cannot capture symbol ‘x’

在引用类型的情况下,如在原始示例中,您真的只对捕获引用的值而不是包含该引用的变量感兴趣.这不会使其在语法上相同,编译器为此而为您创建一个新变量是不合适的.

我们可以重写上面的内容,引入一个变量:

procedure TDemo.Bar(var x: Integer);
var
  y : integer;
begin
  y := x;
  FFoo := procedure
          begin
            WriteLn(y);
          end;
end;

这是允许的,但预期的行为将是非常不同的.在捕获x(不允许)的情况下,我们期望FFoo总是将任何变量作为参数x传递的当前值写入到Bar,而不管在此过程中哪里或何时被更改.我们也预期,即使在创建它的任何范围之后,关闭也会保持变量存活.

然而,在后一种情况下,我们预期FFoo输出y的值,它是变量x的值,就像最后一次调用Bar一样.

回到第一个例子,考虑一下:

program Project1;    
{$APPTYPE CONSOLE}    
type
  TFoo = reference to procedure;    
  TDemo = class
  private
    FFoo : TFoo;
    FBar : string;
    procedure Foo;
  public
    class function Construct(): TDemo;
  end;

procedure TDemo.Foo;
begin
  WriteLn('foo' + FBar);
end;

class function TDemo.Construct: TDemo;
var
  LDemo : TDemo;
begin
  result := TDemo.Create();
  LDemo := result;
  LDemo.FBar := 'bar';
  result.FFoo := LDemo.foo;
  LDemo := nil;
  result.FFoo();  // **access violation
end;

var
 LDemo:TDemo;
begin
  LDemo := TDemo.Construct;
end.

这里很清楚:

result.FFoo := LDemo.foo;

我们还没有为存储在LDemo中的TDemo实例分配方法foo的正常引用,但实际上已经捕获了变量LDemo本身,而不是当时包含的值.将LDemo设置为零后,自然会产生访问冲突,甚至认为在分配作业时引用的对象实例仍然存在.

这与我们简单地将TFoo定义为对象的过程而不是引用过程的行为截然不同.如果我们这样做,那么上面的代码就像一个可能天真地期待的(输出foobar到控制台).

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读