20

给定 C++ 中的类定义

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

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

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

4

4 回答 4

16

基于 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可以在常量表达式中。

于 2013-11-22T09:20:17.937 回答
6

好吧...在 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继承,因为它在运行时放置了基础对象。如果有其他缺陷,我会很高兴。

于 2012-11-01T17:23:34.367 回答
3

不,一般不会。

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

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

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

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

于 2012-11-01T16:42:26.750 回答
0

在 1996 年由 C++ 最初设计者之一 Stanley B. Lippman 撰写的《深入了解 C++ 对象模型》一书中,在第 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:: *'

有没有人有任何关于这里发生的事情的历史?这样的事情还有可能吗?

网络问题——过时的书永远不会消失……

于 2016-11-23T03:18:28.937 回答