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

2cmtqfgy  于 2023-04-01  发布在  其他
关注(0)|答案(4)|浏览(146)

给出一个C++中的类定义

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

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

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

vmdwslir

vmdwslir1#

基于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可以在常量表达式中,这看起来不可能。

fafcakar

fafcakar2#

好吧...在C11中,你实际上可以用常规的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继承,因为它的运行时放置的基本对象。我很高兴看到其他缺陷,如果有。

qojgxg4l

qojgxg4l3#

不,不是一般的。
offsetof宏用于POD(plain old data)结构体,它可以用C0x稍微扩展到标准布局结构体(或其他类似的轻微扩展)。因此,对于那些受限制的情况,您有一个解决方案。
C
给编译器的编写者提供了很大的自由,我不知道有什么条款可以防止某些类对类成员有可变的偏移量--然而,我也不知道为什么编译器要这样做。)
现在,一种保持代码符合标准但仍有偏移量的方法是将数据插入POD(或某些C++0x扩展)子结构,offsetof将在其上工作,然后在该子结构上工作,而不是在整个类上工作。或者您可以放弃标准遵从性。您的结构在类中的偏移量将是未知的,但成员在结构中的偏移量将为。
要问的一个重要问题是“我为什么要这样做,我真的有一个很好的理由吗”?

j2datikz

j2datikz4#

在1996年的书“C对象模型内部”中,由Stanley B. Lippman编写,他是最初的C设计师之一,在第4.4章中提到了指向成员函数的指针
获取非静态数据成员的地址返回的值是该成员在类布局中位置的字节值(加1)。人们可以认为它是一个不完整的值。在访问该成员的实际示例之前,它需要绑定到类对象的地址。
虽然我依稀记得+1是在前世的某个地方,但我以前从未见过或使用过这种语法。

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

至少根据描述,这似乎是一种很酷的方式来获得“几乎”的偏移。
但它似乎不再起作用了,至少在GCC中是这样。
无法使用“int t::”类型的右值初始化“int(t::)”类型的变量'
有人知道这背后的历史吗?这样的事情还有可能发生吗?
网络的问题--过时的书永远不会死。。

相关问题