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

时间:2013-05-17 07:25:36

标签: c++ templates

我在C ++中实现了类似C#的属性类。 所以我必须为内部字段(mother :: _ i)提供属性字段(mother :: i)。

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

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

其次我想到了RProperty的这个指针和它自己的成员指针可以找到所有者的指针。显然,ownerpointer = this - & mother :: i。但是为成员本身提供成员指针会给我编译时错误。我尝试了一个棘手的方法,使用'empty'结构来提供属性的成员指针。但事实证明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;
}

1 个答案:

答案 0 :(得分:4)

首先...

  

所以我必须为内部字段(mother :: _ i)提供属性字段(mother :: i)。

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

明白......

ownerpointer = this - &mother::i

您似乎正在尝试从指针中减去成员指针,而您无法做到这一点。成员指针有点类似于类型布局的偏移量,但这会以两种方式分解......

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

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

  3. 如果你真的想做一个能够识别类型布局的指针运算,它肯定是可能的,但它是一种使用C级特性的C编程技术。在背景上也有一些重要的限制。

    关键的想法是,不使用成员指针作为偏移,而是使用实际的偏移量。这会让你感到类型安全,但是,只要你包装了类型不安全的代码并且绝对确定它是正确的,你应该没问题。

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

    offsetof(structname, membername)
    

    您可以查找该宏的实现,但不要复制它 - 该标准要求编译器提供某种方式来实现可行的宏,但实现有效对于一个编译器可能不适用于另一个。但是,有两种常见的方法是......

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

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

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

    下一个问题来自我链接的offsetof文档......

      

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

    您正在查看某种类型的布局。您还需要将该布局应用于子类型。因为您已经放弃了C ++多态抽象并且直接处理偏移,所以编译器无法为您处理任何运行时布局解析。各种与继承相关的问题会使偏移量计算无效 - 多重继承,虚拟继承,当基数没有时,它具有虚拟指针的子类型。

    所以你需要使用POD结构进行布局。你可以逃脱单一继承,但你不能拥有虚拟方法。但又有另一个烦恼 - POD是一个有点过载的术语,显然与offsetof是否有效无关。具有非POD数据成员的类型不是POD。

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

    我在SO上的问题和答案是here

    offsetof的这个问题可能已在C ++ 11中得到解决 - 我不确定。在任何情况下,即使结构中的成员是非POD,该结构仍将具有在编译时确定的固定布局。即使编译器向你发出警告,偏移也是可以的,幸运的是GCC可以关闭。

    我链接的offsetof文档的下一个问题......

      

    type应为标准布局类(包括联合)。

    这是一个来自C ++ 11的新版本,老实说,我自己并没有真正想过它。

    最后一个问题 - 实际上,指针作为地址的视图无效。当然,编译器将指针实现为地址,但是有很多技术细节,编译器编写者一直在他们的优化器中利用它们。

    一旦你开始做指针算术,你必须非常小心的一个领域是编译器&#34;别名分析&#34; - 它如何决定两个指针是否指向同一个东西(为了决定何时可以安全地将值保存在寄存器中而不是返回内存以查看通过别名指针的写入是否改变了它)。我曾经问this question这个问题,但事实证明我接受的答案是一个问题(我应该回去做一些事情)因为虽然它正确地描述了问题,但它建议的解决方案(使用union-基于双关语)只适用于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*(编译器知道它可以为其他指针类型添加别名)所以它似乎有效,尽管可能有我不记得的细节。对不起 - 这些事情都是 hard ,特别是现在这些日子里的优秀人才有时会在“讨厌的学生”中变得聪明。感觉,如果我绝对必须,我只触摸这个邪恶的代码。

    最后一个问题 - 我已经提到指针不是地址。一个奇怪的是,在某些平台上,两个不同的指针可能映射到不同地址空间中的相同地址 - 例如,参见Harvard Architecture,其具有用于指令的不同地址空间。因此,即使两个指针之间的偏移也是无效的,除非在一定限度内,毫无疑问在标准中详细描述。单个结构是一个结构 - 显然它存在于一个地址空间,可能除了static成员 - 但不要假设指针算术始终有效。


    长话短说 - 是的,可以从成员的地址中减去成员的偏移量以找到结构的地址,但是你必须使用实际的偏移量(而不是成员指针)并且那里限制和技术性可能意味着您甚至无法以这种方式解决您的问题(例如,我不确定您是否能够使用偏移量作为模板参数),并且当然意味着它&#39;比看上去更难。

    最终,外卖建议是,如果您阅读此内容,请将其视为警告。不要做我已经完成的事情。我希望我没有,你可能也会如此。