如何在编译时计算类成员的偏移量?

时间:2012-11-01 15:59:07

标签: c++ templates metaprogramming offset member

给出C ++中的类定义

class A
{
  public:
    //methods definition
    ....

  private:
    int i;
    char *str;
    ....
}

是否可以使用C ++模板元编程计算编译时类成员的偏移量?该类不是POD,并且可以具有虚方法,原始数据和对象数据成员。

4 个答案:

答案 0 :(得分:8)

根据Matthieu M.的答案,但更短,没有宏:

template<typename T, typename U> constexpr size_t offsetOf(U T::*member)
{
    return (char*)&((T*)nullptr->*member) - (char*)nullptr;
}

它被称为:

struct X { int a, b, c, d; }

std::cout << "offset of c in X == " << offsetOf(&X::c);

修改

杰森赖斯是对的。这不会在C ++ 11中产生实际的常量表达式。鉴于http://en.cppreference.com/w/cpp/language/constant_expression中的限制,它看起来不可能 - 特别是没有指针差异,reinterpret_cast可以是常量表达式。

答案 1 :(得分:4)

嗯......在C ++ 11中,你实际上可以使用常规的C ++工具来计算这样的偏移(即,不需要委托给特定的编译器内部函数)。

liveworkspace的行动:

template <typename T, typename U>
constexpr int func(T const& t, U T::* a) {
     return (char const*)&t - (char const*)&(t.*a);
}

然而,这取决于t是对constexpr实例的引用,这可能不适用于所有类。它不禁止T使用virtual方法,甚至也不禁止构造函数,只要它是constexpr构造函数。

尽管如此,这仍是一个障碍。在未评估的上下文中,我们实际上可以使用std::declval<T>()来模拟一个对象;没有。因此,这对对象的构造函数没有特定要求。另一方面,我们可以从这样的上下文中提取的值很少......而且它们确实会对当前的编译器造成问题......好吧,让我们假装它!

liveworkspace的行动:

template <typename T, typename U>
constexpr size_t offsetof_impl(T const* t, U T::* a) {
    return (char const*)t - (char const*)&(t->*a) >= 0 ?
           (char const*)t - (char const*)&(t->*a)      :
           (char const*)&(t->*a) - (char const*)t;
}

#define offsetof(Type_, Attr_)                          \
    offsetof_impl((Type_ const*)nullptr, &Type_::Attr_)

我预见的唯一问题是virtual继承,因为它基于对象的运行时放置。如果有的话,我会很高兴得到其他缺陷。

答案 2 :(得分:2)

不,不是一般的。

对于POD(普通旧数据)结构,存在offsetof offset,并且可以使用C ++ 0x将其略微扩展到标准布局结构(或其他类似的轻微扩展)。因此,对于那些受限制的案例,您有一个解决方案。

C ++为编译器编写者提供了很多自由。我不知道任何会阻止某些类对类成员进行变量偏移的子句 - 但是,我不确定为什么编译器会这样做。 ;)

现在,保持代码标准兼容但仍有偏移的一种方法是将数据粘贴到POD(或某些C ++ 0x扩展)子结构中,offsetof将在其上工作,然后继续工作该子结构而不是整个类。或者你可以放弃标准合规。您的类在您的类中的偏移量是不可知的,但结构中成员的偏移量将是。

要问的一个重要问题是&#34;为什么我要这样做,我真的有充分的理由&#34;?

答案 3 :(得分:0)

1996年出版的“Inside the C ++ Object Model”,由最初的C ++设计师之一Stanley B. Lippman编写,在第4.4章中提到指向成员函数

从获取非静态数据成员的地址返回的值是成员在类布局中的位置的字节值(加1)。人们可以将其视为不完整的价值。在访问成员的实际实例之前,需要将其绑定到类对象的地址。

虽然我模糊地回忆起以前生活中某处的+1,但我以前从未见过或使用过这种语法。

class t
{
public:
    int i;
    int j;
};
int (t::*pmf)() = &t::i;

至少根据描述,这似乎是获得“几乎”偏移的一种很酷的方式。

但它似乎不再起作用了,至少在GCC中是这样。我得到了

Cannot initialize a variable of type 'int (t::*) with an rvalue of type "int t:: *'

有没有人在这里发生什么历史?这样的事情还有可能吗?

网络问题 - 过时的书永远不会死......