Доступ к членам структуры с оператором индекса массива - PullRequest
1 голос
/ 05 июля 2010

Позвольте иметь тип 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));
    }
};

где *Макрос 1007 * (считается правильным):

#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
И еще один,Я не могу использовать только массив и константы для индексации этих полей, они должны быть именованными полями структуры (некоторые макросы требуют этого).

// 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%.Я спрашиваю, можем ли мы ожидать, что некоторые компиляторы поместят неравномерное заполнение между однородными полями.

Ответы [ 5 ]

7 голосов
/ 05 июля 2010

Ваш код не работает с типами 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 с таблицей указателей на члены-данные для оптимизации. Проверьте здесь , чтобы увидеть, о чем я говорю.

1 голос
/ 06 июля 2010

Вы можете достичь того, что вы хотите, используя массив для хранения данных (так что вы можете получить индексированный доступ без использования таблицы поиска) и имея ссылки на различные элементы массива (так что вы можете иметь «именованные» элементы для использовать ваши макросы).

Я не уверен, что требуют ваши макросы, поэтому я не уверен на 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;
}
1 голос
/ 05 июля 2010

Другой способ заключается в специализации шаблона, если то, что вы пытаетесь достичь, все еще является функцией времени компиляции.

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 как функцию-член, но вы не можете специализировать функции-члены шаблона.,Теперь, если это только расширение времени компиляции, которое вы ищете, это позволит избежать проблемы с таблицей поиска в одном из предыдущих постов.Если вам нужно разрешение во время выполнения, тогда вам нужна таблица соответствия.

- Брэд Фелан http://xtargets.heroku.com

0 голосов
/ 06 июля 2010

Если вы уверены, что используемые вами компиляторы собираются сгенерировать правильный код для этого (и я предполагаю, что они будут, предполагая, что 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);

Это ничего не говорит о том, что делать, если утверждения начинают сбой, но вы должны по крайней мере получить предупреждение о том, что предположения не соответствуют действительности ...

(Кстати, вставляйте соответствующие приведения к ссылкам на символы и т. Д., Где это необходимо.Я оставил их для краткости.)

0 голосов
/ 05 июля 2010

Вы не можете этого сделать, потому что компилятор может добавить битые байты между членами, чтобы разрешить заполнение.

Есть два способа сделать то, что вы хотите.

Первый - использовать ключевое слово, специфичное для компилятора, или макрос прагмы, который заставит компилятор не добавлять байты заполнения. Но это не портативно. Тем не менее, это может быть самый простой способ сделать это с вашими требованиями к макросам, поэтому я предлагаю вам изучить эту возможность и подготовиться к добавлению большего количества прагмы при использовании различных компиляторов.

Другой способ - сначала убедиться, что ваши участники выровнены, а затем добавить методы доступа:

struct Foo {

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


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

};
...