热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

由于对齐,TEqualityComparer<T>可能因记录而失败

如何解决《由于对齐,TEqualityComparer<T>可能因记录而失败》经验,为你挑选了1个好方法。

我们最近遇到了一个问题,TDictionary即无法正确查找已包含在字典中的项目.该问题仅发生在64位版本中.我能够将问题分解为这段代码:

var
  i1, i2: TPair;
begin
  FillMemory(@i1, sizeof(i1), $00);
  FillMemory(@i2, sizeof(i1), $01);
  i1.Key := 2;
  i1.Value := -1;
  i2.Key := i1.Key;
  i2.Value := i1.Value;
  Assert(TEqualityComparer>.Default.Equals(i1, i2));
  Assert(TEqualityComparer>.Default.GetHashCode(i1) = TEqualityComparer>.Default.GetHashCode(i2));
end;

断言在Win64版本中失败.由于记录对齐,似乎会出现问题:此TPair的大小为16个字节,但只有12个字节用数据填充.该TEqualityComparer然而通吃16个字节考虑.所以2个记录值可能被视为不相等,尽管所有成员都相等,只是因为内存的先前内容不同

这可以被视为设计中的错误或行为吗?无论如何,这是一个陷阱.这种情况的最佳解决方案是什么?

作为解决方法,可以使用NativeInt而不是Integer,但是这种Integer类型不在我们的控制之下.



1> David Heffer..:

我不认为这是一个错误.行为是设计的.如果没有检查或者可能需要一些编译时间来理解这些类型,就很难为任意结构化类型编写通用比较器.

默认记录比较器只能安全地用于没有填充的类型,并且只包含可以使用朴素二进制比较进行比较的某些简单值类型.例如,浮点类型已经输出,因为它们的比较运算符更复杂.想想NaNs,负零等等.

我认为解决这个问题的唯一有效方法是编写自己的相等比较器.其他人建议默认初始化所有记录实例,但这给这类类型的消费者带来了沉重的负担,并且在某些代码忘记默认初始化的情况下,存在模糊和难以追踪缺陷的风险.

我会TEqualityComparer.Construct用来创建合适的相等比较器.这需要最少量的样板.您提供了两个匿名方法:一个等于函数和一个哈希函数,并Construct返回一个新的比较器.

你可以将它包装在一个泛型类中,如下所示:

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

{$IFOPT Q+}
  {$DEFINE OverflowChecksEnabled}
  {$Q-}
{$ENDIF}
function CombinedHash(const Values: array of Integer): Integer;
var
  Value: Integer;
begin
  Result := 17;
  for Value in Values do begin
    Result := Result*37 + Value;
  end;
end;
{$IFDEF OverflowChecksEnabled}
  {$Q+}
{$ENDIF}

type
  TPairComparer = class abstract
  public
    class function Construct(
      const EqualityComparison: TEqualityComparison>;
      const Hasher: THasher>
    ): IEqualityComparer>; overload; static;
    class function Construct: IEqualityComparer>; overload; static;
  end;


class function TPairComparer.Construct(
  const EqualityComparison: TEqualityComparison>;
  const Hasher: THasher>
): IEqualityComparer>;
begin
  Result := TEqualityComparer>.Construct(
    EqualityComparison,
    Hasher
  );
end;

class function TPairComparer.Construct: IEqualityComparer>;
begin
  Result := Construct(
    function(const Left, Right: TPair): Boolean
    begin
      Result :=
        TEqualityComparer.Default.Equals(Left.Key, Right.Key) and
        TEqualityComparer.Default.Equals(Left.Value, Right.Value);
    end,
    function(const Value: TPair): Integer
    begin
      Result := CombinedHash([
        TEqualityComparer.Default.GetHashCode(Value.Key),
        TEqualityComparer.Default.GetHashCode(Value.Value)
      ]);
    end
  )
end;

我提供了两个重载.如果两种类型的默认比较器足够,则可以使用无参数过载.否则,您可以提供两种匿名方法来定制类型.

对于您的类型,您将获得这样的比较器:

TPairComparer.Construct

这两种简单类型都有可以使用的默认相等比较器.因此Construct可以使用无参数过载.


推荐阅读
author-avatar
qgone
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有