簡體   English   中英

使用數組下標運算符訪問結構成員

[英]Accessing struct members with array subscript operator

假設有一個T型和一個只有T型元素統一的結構。

struct Foo {
    T one,
    T two,
    T three
};

我想以休閑方式訪問它們:

struct Foo {
    T one,
    T two,
    T three

    T &operator [] (int i)
    {
        return *(T*)((size_t)this + i * cpp_offsetof(Foo, two));
    }
};

其中cpp_offsetof宏(被認為是正確的)是:

#define cpp_offsetof(s, m)   (((size_t)&reinterpret_cast<const volatile char&>((((s*)(char*)8)->m))) - 8)

C ++標准不能保證這一點,但是我們可以假設成員之間的距離是固定的偏移量,並且以上是正確的跨平台解決方案嗎?


100%兼容的解決方案是:

struct Foo {
    T one,
    T two,
    T three

    T &operator [] (int i) {
        const size_t offsets[] = { cpp_offsetof(Foo, one), cpp_offsetof(Foo, two), cpp_offsetof(Foo, three) };
        return *(T*)((size_t)this + offsets[i]);
    }
};

snk_kid使用指向數據成員的指針 提出了[edit]標准,兼容和更快的版本 [/ edit]
但它需要額外的查找表,我想避免這種情況。

//編輯
還有一個。 我不能僅使用數組和常量來索引這些字段,它們必須被命名為結構的字段(某些宏需要這樣做)。

//編輯2
為什么必須將這些命名為結構的字段? 什么是宏? 它是一個較大的項目的設置系統。 簡化如下:

struct Foo {
    int one;
    int two;
}
foo;

struct Setting { void *obj, size_t filed_offset, const char *name, FieldType type }

#define SETTING(CLASS, OBJ, FIELD, TYPE) { OBJ, cpp_offsetof(CLASS, FIELD), #OBJ #FIELD, TYPE }

Setting settings[] = {
    SETTING(Foo, foo, one, INT_FIELD),
    SETTING(Foo, foo, two, INT_FIELD)
};

再說一次:我不是在尋找100%兼容的解決方案,而是99%的兼容解決方案。 我問我們是否可以期望某些編譯器將非均勻填充放在統一字段之間。

您的代碼不適用於使用虛擬成員函數的NON-POD類型。 使用指向數據成員的指針,有一種符合標准(高效)的方法來實現您要執行的操作:

template< typename T >
struct Foo {

    typedef size_t size_type;

private:

    typedef T Foo<T>::* const vec[3];

    static const vec v;

public:

    T one;
    T two;
    T three;

    const T& operator[](size_type i) const {
        return this->*v[i];
    }

    T& operator[](size_type i) {
        return this->*v[i];
    }
};

template< typename T >
const typename Foo<T>::vec Foo<T>::v = { &Foo<T>::one, &Foo<T>::two, &Foo<T>::three };

只需確保將const每個與指向數據成員的指針表一起使用即可獲得優化。 檢查這里查看我在說什么。

如果您要實現的目標仍然是編譯時功能,則可以使用模板專門化。

class Foo {
    T one;
    T two;
    T three; 
};

template <int i> T & get(Foo& foo);

template T& get<1>(Foo& foo){ return foo.one;}
template T& get<2>(Foo& foo){ return foo.two;}
template T& get<3>(Foo& foo){ return foo.three;}

將get定義為成員函數會很好,但是您不能專門化模板成員函數。 現在,如果這只是您要尋找的編譯時擴展,那么這將避免先前文章之一的查找表問題。 如果需要運行時解析,則顯然需要一個查找表。

-Brad Phelan http://xtargets.heroku.com

您可能能夠使用數組保存數據來實現所需的功能(這樣就可以在不使用查找表的情況下獲得索引訪問)並擁有對各種數組元素的引用(因此您可以使用“命名”元素供您使用)宏)。

我不確定您的宏需要什么,因此我不是100%確信這會起作用,但是可能。 另外,我不確定查找表方法的微小開銷是否值得避免太多。 另一方面,我認為我在這里建議的方法沒有比指針表方法更復雜的方法,因此在這里供您考慮:

#include <stdio.h>

template< typename T >
struct Foo {

private:    
    T data_[3];

public:

    T& one;
    T& two;
    T& three;

    const T& operator[](size_t i) const {
        return data_[i];
    }

    T& operator[](size_t i) {
        return data_[i];
    }

    Foo() :
        one( data_[0]),
        two( data_[1]),
        three( data_[2])
        {};

};


int main()
{
    Foo<int> foo;

    foo[0] = 11;
    foo[1] = 22;
    foo[2] = 33;

    printf( "%d, %d, %d\n", foo.one, foo.two, foo.three);

    Foo<int> const cfoo( foo);

    printf( "%d, %d, %d\n", cfoo[0], cfoo[1], cfoo[2]);

    return 0;
}

您不能這樣做,因為編譯器可以在成員之間添加無效字節以允許填充。

有兩種方法可以做您想要的。

第一種是使用特定於編譯器的關鍵字或編譯指示宏,這將強制編譯器不添加填充字節。 但這不是便攜式的。 也就是說,這可能是滿足您的宏要求的最簡單方法,因此我建議您探索這種可能性,並准備在使用不同的編譯器時添加更多的實用性。

另一種方法是首先確保您的成員對齊,然后添加訪問器:

struct Foo {

   T members[ 3 ]; // arrays are guarrantied to be contigu


   T& one() { return members[0]; } 
   const T& one() const { return members[0]; } 
   //etc... 

};

如果您確定所使用的編譯器將為此生成正確的代碼(我想他們會假設T始終不是引用類型),那么最好的辦法就是使用某種檢查結構是否按照您的想法進行布局。 我想不出任何特殊原因在相同類型的相鄰成員之間插入非均勻填充,但是如果您手動檢查結構布局,則至少會知道它是否發生。

例如,如果結構(S)恰好具有N個類型T的成員,則可以在編譯時使用sizeof來檢查它們是否緊密包裝:

struct S {
    T a,b,c;
};

extern const char check_S_size[sizeof(S)==3*sizeof(T)?1:-1];

如果編譯成功,那么它們將被緊密包裝,因為沒有其他空間。

如果您剛好有N個成員,並且要確保它們直接一個接一個地放置,則可以使用offsetof做類似的事情:

class S {
    char x;
    T a,b,c;
};

extern const char check_b_offset[offsetof(S,b)==offsetof(S,a)+sizeof(T)?1:-1];
extern const char check_c_offset[offsetof(S,c)==offsetof(S,b)+sizeof(T)?1:-1];

根據編譯器的不同,這可能必須成為運行時檢查,可能不使用offsetof -無論如何,您可能要對非POD類型執行此操作,因為未為它們定義offsetof

S tmp;
assert(&tmp.b==&tmp.a+1);
assert(&tmp.c==&tmp.b+1);

這並沒有說明斷言開始失敗時的處理方法,但是您至少應該得到一些警告,認為這些假設不成立...

(順便說一句,在char引用中插入適當的強制類型轉換,以此類推。為簡潔起見,我省略了它們。)

暫無
暫無

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

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