【问题标题】:Offset from member pointer without temporary instance没有临时实例的成员指针的偏移量
【发布时间】:2012-08-27 11:52:06
【问题描述】:

当提供一个指向该变量的指针时,我想获得标准布局成员变量的偏移量。我不能使用offsetof,因为我有一个指针而不是一个名字。我当前的代码看起来像这样,我想知道是否有一种符合标准的方法来摆脱 dummy 变量。

template<class T>
struct {
  ptrdiff_t get_offset( int (T::*mem) )
  {
    T dummy;
    return reinterpret_cast<char*>(&(dummy.*mem)) 
      - reinterpret_cast<char*>(&dummy);
  }
};

这个函数应该只能用int成员变量点来调用(这是故意的)。

我很确定编译器实际上并没有创建 dummy 变量,但如果我能摆脱它仍然会很好。我不能使用空指针,因为未定义取消引用 null(尽管它可能适用于所有常见的编译器)。 C++03 解决方案会很好,或者 C++11 解决方案也很有趣(但我现在不能使用)。

注意:我已经知道这只是符合标准,T 是标准布局类型。

【问题讨论】:

  • 返回类型应该是ptrdiff_t,我想,你应该使用std::distance。函数应该是static
  • @KerrekSB,是的。我在 GCC 中使用完整/额外警告进行编译,但我猜是 size_t == ptrdiff_t,所以没有警告。
  • ptrdiff_t 已签名...无论如何,我想dummy 也应该是静态的。
  • @Andrey,但这是一个实现细节,在每个编译器中可能会有所不同。
  • 另一个小的改进是使用类似Boost.AddressOf的东西,以确保这适用于重载operator&amp;的类型。

标签: c++


【解决方案1】:

怎么样:

template<class T>
struct {
  ptrdiff_t get_offset( int (T::*mem) )
  {
    union {
      int used;
      T unused;
    } dummy;
    return reinterpret_cast<char*>(&(dummy.unused.*mem)) 
      - reinterpret_cast<char*>(&dummy.unused);
  }
};

联合成员的地址不依赖于正在构造的联合成员。已经在 C++03 中工作,但仅适用于 POD。

【讨论】:

  • 所以这个版本基本上消除了对T::T()T:~T()的任何调用。它仍然有一个虚拟机,但抽象机执行的代码更少。
  • 嗯,显然你需要内存,否则你不能形成指针。
  • 这仍然是 UB,因为您正在使用 union 的组件,该组件在使用时未处于活动状态。因此,它比reinterpret_cast&lt;T*&gt;(nullptr) 更好吗?更糟糕的是,您最终会为T 创建一堆堆栈空间。可能有一个系统可以做到这一点,但 nullptr 没有,但反过来也可能是正确的。
  • @Yakk:哪个确切位是“使用未激活的联合组件”?请记住,我可能(必须)为非活动成员形成一个左值以使其处于活动状态。 IE。 dummy.unused = T() 写入非活动成员,使其处于活动状态。我无法读取非活动成员的信息。
  • 嗯,这是一个完全合理的左值,您可以向其写入一个新整数,这当然会使unused 使用/激活。在内心深处,阅读不活跃的工会成员时出现问题的是lvalue-to-rvalue conversion(或glvalue-to-prvalue conversion,每个脚注53)。但我们没有这种转换。
【解决方案2】:

恐怕不存在满足 OP 要求的符合标准的解决方案。

我可以给出几个不合规的。

template<class T>
  size_t get_offset( int (T::*mem) )
    {
    return reinterpret_cast<char*>(&(((T*)nullptr)->*mem))-reinterpret_cast<char*>(nullptr);
    }

这很有趣,但以下在 VC2010 中有效,利用 offsetof 作为宏。

template<class T>
  size_t get_offset( int (T::*mem) )
    {
    return offsetof(T, *mem);
    }

【讨论】:

  • 您正在通过 -&gt;* 运算符取消引用空指针。考虑等效的(*(T*)nullptr).*mem
  • 您仍在取消引用空指针。仅仅因为offsetof做到了,并不意味着它不是UB。
  • @PeterAlexander:是的,我知道...恐怕不存在满足 OP 要求的解决方案。需要一个虚拟变量或取消引用 NULL。
  • @Andrey,您可以将“否”作为答案,如果没有其他人找到解决方案,我必须接受“否”。我已经怀疑没有其他符合标准的解决方案了。
【解决方案3】:

那么怎么样:

template<class T>
struct {
    ptrdiff_t get_offset( int (T::*mem) )
    {
        return 
        ( &reinterpret_cast<const char&>( 
            reinterpret_cast<const T*>( 1 )->*mem ) 
          - reinterpret_cast<const char*>( 1 )      );
    }
};

..?

这避免了同时使用虚拟和取消引用 null。它适用于我尝试过的所有编译器。转换为 char 引用然后获取地址(而不是获取地址然后转换为 char 指针)可能看起来不寻常,但它避免了某些编译器上的警告/错误。

【讨论】:

    猜你喜欢
    • 2011-08-02
    • 1970-01-01
    • 2012-02-22
    • 2013-03-09
    • 2019-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    相关资源
    最近更新 更多