第一的...
所以我必须提供对内部字段(mother::_i)到属性字段(mother::i)的访问权限。
以下划线开头的标识符在 C++ 中是保留的——只有编译器和它的库应该使用它们。包含双下划线的标识符也被保留。但是,带有单个尾随下划线的标识符(例如,i_
可以)。
进入正题...
ownerpointer = this - &mother::i
看起来您正在尝试从指针中减去成员指针,这是您无法做到的。成员指针有点像类型布局中的偏移量,但这以两种方式分解......
这不是他们旨在提供的抽象。
无论如何它都不准确 - 一旦您允许多重继承和虚拟继承,特定成员在类型中出现的偏移量不仅取决于它在定义它的基本类型中的位置,还取决于您的子类型重新看。
如果你真的想做知道类型布局的指针运算,那当然是可能的,但它是一种使用 C 级特性的 C 编程技术。上下文也有一些重大限制。
关键思想是不要尝试使用成员指针作为偏移量,而是使用实际的偏移量。这会花费您的类型安全性,但是,只要您包装类型不安全的代码并绝对确定它是正确的,就应该没问题。
基本工具是offsetof
,它是 C++ 继承自 C...
offsetof(structname, membername)
您可以查找该宏的实现,但不要复制它 - 标准要求编译器提供某种方式来实现有效的宏,但适用于一个编译器的实现可能不适用于另一个。但是,两种常见的方法是...
- 查看地址为零的结构的虚构实例中成员的地址。与此有关的问题(例如,想象中的实例显然没有有效的虚拟指针)是某些限制的部分原因。
- 使用编译器提供的特殊“内在”函数,这也是保留那些带下划线的标识符的原因之一。
原则上,使用该偏移量,您可以将指针转换为char*
via void*
,进行算术运算,然后再次转换回所需的类型。
第一个问题很明显——某些成员(即static
那些成员)在每个实例中都没有固定的偏移量,无论实例如何,它们都位于固定地址。显而易见,但也许最好说出来。
下一个问题来自offsetof
我链接的那个文档......
type 应为 POD 类(包括联合)。
您正在查看类型的布局。您还需要将该布局应用于子类型。因为您已经放弃了 C++ 多态抽象并且直接处理偏移量,所以编译器无法为您处理任何运行时布局解析。各种与继承相关的问题会使偏移计算无效 - 多重继承、虚拟继承、当基类没有虚拟指针时具有虚拟指针的子类型。
因此,您需要使用 POD 结构进行布局。您可以摆脱单继承,但不能拥有虚拟方法。但是还有另一个烦恼——POD 是一个有点过分的术语,显然不仅仅与是否offsetof
有效有关。具有非 POD 数据成员的类型不是 POD。
我用多路树数据结构解决了这个问题。我曾经offsetof
实现数据结构(因为不同的时间)。我将它包装在一个模板中,该模板使用struct
andoffsetof
来确定节点布局。在整个系列的编译器和编译器版本中,这很好,直到我切换到 GCC 版本,它开始到处发出警告。
我关于这个的问题和答案在这里。
这个问题offsetof
可能已经在 C++11 中得到解决——我不确定。在任何情况下,即使结构中的成员是非 POD,该结构仍将具有在编译时确定的固定布局。即使编译器向您抛出警告,偏移量也是可以的,幸运的是,在 GCC 中可以将其关闭。
offsetof
我链接的那个文档中的下一个问题......
type 应为标准布局类(包括联合)。
这是来自 C++11 的新版本,老实说,我自己并没有真正考虑过。
最后一个问题——实际上,将指针视为地址是无效的。当然,编译器将指针实现为地址,但是有很多技术细节,编译器编写者一直在他们的优化器中利用这些。
一旦你开始进行指针运算,你必须非常小心的一个领域是编译器的“别名分析”——它如何决定两个指针是否指向同一个东西(为了决定什么时候它可以安全地将值保存在寄存器中而不是返回内存以查看通过别名指针的写入是否更改了它)。我曾经问过这个问题,但事实证明我接受的答案是一个问题(我可能应该回去做点什么)因为虽然它正确描述了问题,但它建议的解决方案(使用基于联合的双关语)仅对 GCC 正确,C++ 标准不保证。
最后,我的解决方案是将指针算术(和指针)隐藏char*
在一组函数中......
inline void* Ptr_Add (void* p1, std::ptrdiff_t p2)
{
return (((char*) p1) + p2);
}
inline void* Ptr_Sub (void* p1, std::ptrdiff_t p2)
{
return (((char*) p1) - p2);
}
inline std::ptrdiff_t Ptr_Diff (void* p1, void* p2)
{
return (((char*) p1) - ((char*) p2));
}
inline bool Ptr_EQ (void* p1, void* p2) { return (((char*) p1) == ((char*) p2)); }
inline bool Ptr_NE (void* p1, void* p2) { return (((char*) p1) != ((char*) p2)); }
inline bool Ptr_GT (void* p1, void* p2) { return (((char*) p1) > ((char*) p2)); }
inline bool Ptr_GE (void* p1, void* p2) { return (((char*) p1) >= ((char*) p2)); }
inline bool Ptr_LT (void* p1, void* p2) { return (((char*) p1) < ((char*) p2)); }
inline bool Ptr_LE (void* p1, void* p2) { return (((char*) p1) <= ((char*) p2)); }
这种std::ptrdiff_t
类型也很重要——指针的位宽不能保证与 a 的位宽匹配long
。
在这些函数之外,所有指针要么是正确的类型,要么是void*
. C++void*
进行了特殊处理(编译器知道它可以为其他指针类型起别名),因此它似乎可以工作,尽管可能有一些我不记得的细节。抱歉 - 这些事情很难,尤其是现在优化器有时在“令人讨厌的学究”意义上很聪明,我只有在绝对必要时才会触碰这个邪恶的代码。
最后一个问题 - 我已经提到指针不是地址。一个奇怪的是,在某些平台上,两个不同的指针可能会映射到不同地址空间中的同一地址 - 例如,参见哈佛架构,它具有不同的指令地址空间。因此,即使两个指针之间的偏移量在一定范围内也是无效的,这无疑在标准中进行了复杂的详细描述。一个单一的结构就是一个单一的结构——显然它存在于一个地址空间上,static
成员可能例外——但不要仅仅假设指针算法总是有效的。
长话短说 - 是的,可以从成员的地址中减去成员的偏移量以找到结构的地址,但是您必须使用实际的偏移量(而不是成员指针)并且存在限制和技术性可能意味着你甚至不能以这种方式解决你的问题(例如,我不确定你是否能够使用偏移量作为模板参数),当然这意味着它比看起来更难。
最终,外卖建议是,如果您阅读本文,请将其视为警告。不要做我做过的事情。我希望我没有,你可能也会。