【注】C/C++ 中有一个 __offset 用来计算一个数据成员相对于起始地址偏移的函数,这样就可以在子结点中,不用记录上一级的地址,就可以达到回溯父一级的对象实例。
Delphi 要实现的同样的效果,有几种选择:
- 基于 RTTI,通过对应的类型的信息,然后取对应的 TRttiField 的 Offset 的值,此种方法我们不在此展开,具体参考 QDAC 源码里各种 Rtti 序列化代码。
- 直接通过简单的数据运算实现,我们知道Delphi 支持取地址操作,我们只需要取当前成员的地址,然后再减去当前成员相对偏移就可以。
下面是一个简单的例子:
type
TParent=record
FieldA:Integer;
FieldB:Integer;
end;
PParent=^TParent;
...
//
function GetParentAddr(const ABAddr:PInteger):PParent;inline;
begin
Result:=PParent(IntAddr(ABAddr)-IntPtr(@PParent(nil).FieldB);
end;
是不是比用 RTTI 简单多了,而且效率也嘎嘎高。由此带来的好处:
- 不需要单独存储指向父的地址,复合数据类型的定义更简洁。
- 不需要维护指向父的地址,代码也会更简洁。
- 由于只是简单的减法运算,效率很高。
【限制】
- 只适用于复合类型,当然简单类型也没有意义。
- 要明确知道父容器的类型,如上面你得知道是父的类型是 PParent,如果父容器是多种类型,你必需有办法区分或者将父对象的成员地址对齐,保证像上面的 FieldB 在内存中偏移是同一个位置。
- 没有容错空间,也就是说,如示例中用户传的 ABAddr 地址不是一个 TParent 类型的成员的地址,那么你仍将得到一个 PParent 地址,但它明显是无效的,会出现 AV 错误。当然这一点需要用户编码附页来保证的,无论是使用 RTTI 还是这种直接计算,这个都是开发人员的责任。