提供指向成员本身的成员指针

provide member pointer to the member itself

本文关键字:成员 指针      更新时间:2023-10-16

我正在用c++实现类似c#的属性类。所以我必须提供对内部字段(mother::_i)和属性字段(mother:: I)的访问。

我找到了一些解决方案,但没有完美的。

首先,我做了一个方法来提供所有者(母亲在这种情况下)的指针运行时通过调用方法,如RProperty<…>::SetOwner(母亲&)。但是它需要额外的代码来使用我的属性类,并且在运行时需要成本。

其次,我想到了这个RProperty的指针和它自己的成员指针可以找到所有者的指针。显然,ownerpointer = this - &mother::i。但是为成员本身提供成员指针会导致编译时错误。我尝试了一个棘手的方法使用"空"结构来提供成员指针的属性。但是sizeof(struct empty)不是零。每个实例都会消耗不必要的额外内存。我在这个问题上耽搁了好几天。

谁有好主意?:)

代码可以工作但不完美:

#include "stdafx.h"
struct empty{};
template<typename TCLASS, typename TFIELD>
class RPropertyBase
{
protected:
RPropertyBase(){ }
TCLASS& getOwner() const {  };
};
template<typename TCLASS, typename TFIELD, TFIELD TCLASS::*PFIELD, empty TCLASS::*PTHIS>
class RProperty : RPropertyBase<TCLASS, TFIELD>
{
protected:
TCLASS& getOwner() const { return *(TCLASS*)((unsigned int)this-(unsigned int)&(((TCLASS*)0)->*PTHIS)-sizeof(empty) ); }
public:
RProperty<TCLASS, TFIELD, PFIELD, PTHIS>& operator=(const TFIELD& A){ getOwner().*PFIELD = A; return *this; }
operator TFIELD&() const { return getOwner().*PFIELD; }
};
class mother
{
int _i;
template<typename C>
struct __Propertyi : public RProperty<C, int, &C::_i, &C::_empty>
{
using RProperty<C, int, &C::_i, &C::_empty>::operator=;
};
public:
empty _empty;
__Propertyi<mother> i;
};
int _tmain(int argc, _TCHAR* argv[])
{
mother a;
a.i = 1;
int bb = (a.i);
return 0;
}

First…

所以我必须提供对内部字段(mother::_i)对属性字段(mother:: I)的访问。

以下划线开头的标识符在c++中是保留的——只有编译器和它的库应该使用它们。包含双下划线的标识符也是保留的。但是,带有单个下划线的标识符(如i_)是可以的。

开门见山…

ownerpointer = this - &mother::i

看起来你试图从一个指针中减去一个成员指针,这是你不能做的。成员指针有点像类型布局中的偏移量,但这分为两种方式…

  1. 这不是他们设计提供的抽象。

  2. 无论如何都是不准确的——一旦允许多重继承和虚继承,特定成员在类型中出现的偏移量不仅取决于它在定义它的基类型中的位置,还取决于你正在查看的是哪个子类型。

如果你真的想做指针算术,知道类型的布局,这当然是可能的,但这是一种使用C级特性的C编程技术。在上下文方面也有一些明显的限制。

关键思想是使用实际的偏移量,而不是试图使用成员指针作为偏移量。这会牺牲你的类型安全,但是,只要你包装了类型不安全的代码,并确保它是绝对正确的,你应该没有问题。

基本工具是offsetof,这是c++从C继承的宏…

offsetof(structname, membername)

您可以查找该宏的实现,但不要复制它——标准要求编译器提供一些方法来实现工作的宏,但是在一个编译器上工作的实现可能不适用于另一个编译器。然而,两种常见的方法是……

  • 查看地址为0的结构体虚实例中的成员地址。这个问题(例如,虚拟实例显然没有有效的虚指针)是一些限制的部分原因。
  • 使用编译器提供的特殊的"内在"函数,这就是为什么那些带下划线的标识符被保留的原因之一。

使用该偏移量,原则上,您可以通过void*将指针强制转换为char*,进行算术运算,然后再次强制转换为所需的类型。

第一个问题很明显-一些成员(即static成员)在每个实例中都没有固定的偏移量,它们在固定的地址,而不考虑实例。显而易见,但也许最好说出来。

下一个问题是来自offsetof文档我链接…

类型应为POD类(包括联合)。

你正在看一个字体的布局。您还需要将该布局应用于子类型。因为您抛弃了c++多态抽象,而直接处理偏移量,编译器无法为您处理任何运行时布局解析。各种与继承相关的问题会使偏移量计算无效——多重继承、虚继承、在基类没有虚指针时子类型有虚指针。

所以你需要用POD结构体来做你的布局。您可以使用单继承,但不能使用虚方法。但还有另一个烦恼- POD是一个有点过载的术语,显然不仅仅与offsetof是否有效有关。具有非POD数据成员的类型不是POD。

我在使用多路树数据结构时遇到了这个问题。我使用offsetof来实现数据结构(因为时间不同)。我将其包装在一个模板中,该模板使用structoffsetof来确定节点布局。在一系列编译器和编译器版本中,这很好,直到我切换到GCC的一个版本,它开始到处发出警告。

我关于SO的问题和答案在这里。

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类型也很重要——指针的位宽不能保证与long的位宽匹配。

在这些函数之外,所有指针要么是它们的正确类型,要么是void*。c++特别对待void*(编译器知道它可以别名其他指针类型),所以它似乎可以工作,尽管可能有细节我不记得了。对不起——这些东西很难,尤其是现在的优化器有时在"令人讨厌的迂腐"的意义上是聪明的,我只在绝对必要的时候才碰这些邪恶的代码。

最后一个问题——我已经提到过指针不是地址。一个奇怪的现象是,在某些平台上,两个不同的指针可能映射到不同地址空间中的相同地址——例如,哈佛体系结构的指令有不同的地址空间。因此,即使两个指针之间的偏移量在一定范围内也是无效的,毫无疑问,在标准中有复杂的细节描述。单个结构体就是单个结构体——显然它存在于一个地址空间中,static成员可能例外——但不要仅仅假设指针算术总是有效的。


长话短说——是的,可以从成员的地址中减去成员的偏移量来找到结构体的地址,但是你必须使用实际的偏移量(不是成员指针),并且存在一些限制和技术问题,这可能意味着你甚至不能用这种方式解决问题(例如,我不确定你是否能够使用偏移量作为模板参数),当然意味着它比看起来更难。

最后,我的建议是,如果你读了这篇文章,把它当作一个警告。不要做我做过的事。我希望我没有,也许你也会。