gpt4 book ai didi

delphi - 由于对齐,TEqualityComparer 可能会失败

转载 作者:行者123 更新时间:2023-12-03 14:52:24 28 4
gpt4 key购买 nike

我们最近遇到了 TDictionary<T> 的问题。无法正确查找已包含在字典中的项目的实例。该问题仅出现在 64 位版本中。我能够将问题分解为以下代码:

var
i1, i2: TPair<Int64,Integer>;
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<TPair<Int64,Integer>>.Default.Equals(i1, i2));
Assert(TEqualityComparer<TPair<Int64,Integer>>.Default.GetHashCode(i1) = TEqualityComparer<TPair<Int64,Integer>>.Default.GetHashCode(i2));
end;

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

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

作为解决方法,人们可以使用 NativeInt而不是Integer ,但是这个Integer类型不受我们控制。

最佳答案

我不认为这是一个错误。该行为是设计使然。如果没有检查或可能有一些编译时支持来理解这些类型,就很难为任意结构化类型编写通用比较器。

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

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

我会使用TEqualityComparer<T>.Construct创建合适的相等比较器。这需要最少数量的样板文件。您提供两个匿名方法:一个 equals 函数和一个哈希函数,以及 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<TKey, TValue>(
const EqualityComparison: TEqualityComparison<TPair<TKey, TValue>>;
const Hasher: THasher<TPair<TKey, TValue>>
): IEqualityComparer<TPair<TKey, TValue>>; overload; static;
class function Construct<TKey, TValue>: IEqualityComparer<TPair<TKey, TValue>>; overload; static;
end;


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

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

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

对于您的类型,您将获得如下比较器:

TPairComparer.Construct<Int64, Integer>

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

关于delphi - 由于对齐,TEqualityComparer<T> 可能会失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43229885/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com