使用数组下标运算符访问struct成员

时间:2010-07-05 09:17:40

标签: c++ cross-platform memory-alignment

让类型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]);
    }
};

[edit]标准,合规且更快的版本由 snk_kid using pointers to data members [/ 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 个答案:

答案 0 :(得分: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与指向数据成员的指针表一起使用即可获得优化。检查here,看看我在说什么。

答案 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定义为成员函数会很好,但你不能 专门化模板成员函数。现在,如果这只是一个编译时间 你正在寻找扩展然后这将避免查找表 以前的一个帖子的问题。如果需要运行时解析 那你显然需要一个查找表。

- 布拉德费伦 http://xtargets.heroku.com

答案 2 :(得分:1)

您可能能够使用数组来保存数据(因此您可以在不使用查找表的情况下获得索引访问权限)以及对各种数组元素的引用(因此您可以使用'named'元素由宏使用。)

我不确定你的宏需要什么,所以我不是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;
}

答案 3 :(得分:0)

你不能,因为编译器可以在成员之间添加死字节以允许填充。

有两种方法可以做你想要的。

第一种是使用特定于编译器的关键字或编译指示宏,这将强制编译器不添加填充字节。但这不便携。 这说明它可能是最简单的宏观要求,所以我建议你探索这种可能性,并准备在使用不同的编译器时添加更多的pragma。

另一种方法是首先确保你的成员对齐,然后添加访问者:

struct Foo {

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


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

};

答案 4 :(得分:0)

如果您确定您正在使用的编译器将为此生成正确的代码(我想他们会这样做,假设T不是参考类型),最好的办法就是放入某种检查结构是按照你的想法布局的。我想不出在同一类型的相邻成员之间插入非均匀填充的任何特殊原因,但如果你手工检查结构布局,那么你至少会知道它是否发生。

例如,如果struct(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];

根据编译器的不同,这可能必须成为运行时检查,可能不会使用offsetof - 您可能希望对非POD类型执行此操作,因为offsetof未定义对他们来说。

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

如果断言开始失败,这并没有说明如何做,但你应该至少得到一些警告,假设假设不正确......

(顺便说一句,在适当的时候将适当的强制转换插入char引用等等。为了简洁,我将它们遗漏了。)