1

型 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
そしてもう1つ。これらのフィールドにインデックスを付けるために配列と定数だけを使用することはできません。構造体のフィールドに名前を付ける必要があります (一部のマクロではそれが必要です)。

//EDIT2
なぜそれらは構造体の名前付きフィールドでなければならないのですか? マクロとは何ですか?より大きなプロジェクトの設定システムです。単純化すると、次のようになります。

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% 互換性のあるソリューションを探しています。一部のコンパイラが均一なフィールド間に不均一なパディングを配置することを期待できるかどうかを尋ねています。

4

5 に答える 5

7

コードは、仮想メンバー関数を使用するような 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 every を使用していることを確認してください。ここをチェックして、私が話していることを確認してください。

于 2010-07-05T09:36:55.017 に答える
1

もう1つの方法は、達成しようとしていることがコンパイル時の機能である場合に、テンプレートを特殊化することです。

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をメンバー関数として定義すると便利ですが、テンプレートメンバー関数を特殊化することはできません。これがあなたが探しているコンパイル時の拡張だけである場合、これは以前の投稿の1つのルックアップテーブルの問題を回避します。実行時の解決が必要な場合は、明らかにルックアップテーブルが必要です。

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

于 2010-07-05T13:10:32.430 に答える
1

配列を使用してデータを保持し(ルックアップテーブルを使用せずにインデックス付きアクセスを取得できるように)、さまざまな配列要素への参照を使用して(「名前付き」要素を使用できるように)、目的を達成できる場合があります。マクロ)。

マクロに何が必要かわからないので、これが機能するかどうかは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;
}
于 2010-07-05T22:59:51.380 に答える
0

使用しているコンパイラがこれに適したコードを生成すると確信している場合 (T が参照型でないと仮定すると、生成すると思います)、最善の方法は、何らかの種類の構造体が思いどおりに配置されていることを確認してください。同じ型の隣接するメンバー間に不均一なパディングを挿入する特定の理由は考えられませんが、構造体のレイアウトを手動で確認すると、少なくともそれが発生するかどうかがわかります。

たとえば、構造体 (S) に T 型のメンバーが正確に N 個ある場合、次のようにして、それらが密にパックされていることをコンパイル時に確認できます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];

コンパイラによっては、これを実行時チェックにする必要があり、 -- を使用しない可能性があります。これは、POD 以外の型に対して定義されていないoffsetofため、とにかく実行したい場合があります。offsetof

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

これは、アサートが失敗し始めた場合の対処法については何も述べていませんが、少なくとも、仮定が正しくないという警告が表示されるはずです...

(ちなみに、必要に応じて char 参照などに適切なキャストを挿入してください。簡潔にするために省略しました。)

于 2010-07-05T23:29:20.487 に答える
0

コンパイラはパディングを許可するためにメンバー間にデッドバイトを追加できるため、できません。

やりたいことをやるには2つの方法があります。

1 つ目は、コンパイラ固有のキーワードまたはプラグマ マクロを使用して、コンパイラがパディング バイトを追加しないようにすることです。しかし、それは移植性がありません。とはいえ、これがマクロ要件で最も簡単な方法かもしれないので、この可能性を検討し、別のコンパイラを使用する場合はプラグマを追加する準備をすることをお勧めします。

もう 1 つの方法は、最初にメンバーが整列していることを確認してから、アクセサーを追加することです。

struct Foo {

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


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

};
于 2010-07-05T09:28:52.477 に答える