簡體   English   中英

如何在編譯時計算類成員的偏移量?

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

給定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);

編輯:

傑森賴斯是對的。 這不會在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實例在這里,這可能並不適用於所有類。 它不禁止T擁有virtual方法,甚至也不構造構造函數,只要它是constexpr構造函數即可。

盡管如此,這仍是一個障礙。 在未std::declval<T>()上下文中,我們實際上可以使用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繼承,因為它基於對象的運行時放置。 如果有的話,我會很高興得到其他缺陷。

不,不是一般的。

對於POD(普通舊數據)結構,存在offset的宏,並且可以使用C ++ 0x將其略微擴展到標准布局結構(或其他類似的輕微擴展)。 因此對於那些受限制的案例,您有一個解決方案。

C ++為編譯器編寫者提供了很大的自由。 我不知道任何會阻止某些類對類成員進行變量偏移的子句 - 但是,我不確定編譯器為什么會這樣做。 ;)

現在,保持代碼標准兼容但仍有偏移的一種方法是將數據粘貼到POD(或某些C ++ 0x擴展)子結構中,offsetof將在該子結構上工作,然后處理該子struct而不是整個類。 或者你可以放棄標准合規。 您的類在您的類中的偏移量將是未知的,但結構中的成員的偏移量將是。

一個重要的問題是“我為什么要這樣做,我真的有充分理由”嗎?

在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:: *'

有沒有人在這里發生什么歷史? 這樣的事情還有可能嗎?

網絡問題 - 過時的書永遠不會死...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM