如何在编译时计算班级成员的偏移

How to calculate offset of a class member at compile time?

本文关键字:成员 计算 编译      更新时间:2023-10-16

在C

中给定一个类定义
class A
{
  public:
    //methods definition
    ....
  private:
    int i;
    char *str;
    ....
}

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

基于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);

编辑:

Jason Rice是正确的。这不会在C 11中产生实际的恒定表达。考虑到http://en.cppreference.com/w/cpp/language/constant_expression中的限制,看上去不可能 - 尤其没有指针差异,并且reinterpret_cast可以处于常数表达式中。

好...在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实例的引用,该实例可能不适用于所有类。但是,只要它是constexpr构造函数,它都不禁止T具有virtual方法,甚至禁止使用构造函数。

仍然,这是一个障碍。在未评估的上下文中,我们实际上可以使用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继承,因为它的运行时放置基本对象。如果有。

no,通常不是。

存在用于POD(普通旧数据)结构的宏观偏移,并且可以用C 0x稍微扩展到标准布局结构(或其他类似的轻微扩展)。因此,对于那些受限案件,您有一个解决方案。

C 提供了很多编译器作家的自由。我不知道有任何条款可以阻止某些类对班级成员的变量偏移 - 但是,我不确定为什么编译器会这样做。;)

现在,一种使您的代码标准符合代码标准但仍然存在偏移的方法是将您的数据粘贴到POD(或某些C 0x扩展程序)子结构中,然后在哪个oftsit上进行工作,然后进行工作。该子结构而不是整个课程。或者您可以交出标准的合规性。尚不清楚在类中的结构的偏移,但结构内会员的偏移是。

要问的一个重要问题是"我为什么要这个,我真的有一个充分的理由"?

在1996年的书中" C 对象模型内部",由Stanley B. Lippman(原始C 设计师之一)撰写,提到了第4.4章的指针到成员的功能

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

我隐约记得 1从前世的某个地方中的某个地方,但我以前从未见过或使用过这种语法。

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

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

,但至少在GCC中似乎不再起作用。我有一个错误:

不能用类型的rvalue int int t :: *'

初始化类型'int(t :: *)的变量

有人在这里发生了任何历史吗?这样的事情仍然可以吗?

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