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]
但它需要额外的查找表,我试图避免这种情况。

//编辑
还有一个。我不能只使用数组和常量来索引这些字段,它们必须被命名为结构的字段(某些宏需要)。

//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

您的代码不适用于非 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

如果您要实现的目标仍然是编译时功能,另一种方法是使用模板专业化。

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

于 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

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

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

第一种是使用编译器特定的关键字或编译指示宏,这将强制编译器不添加填充字节。但这不是便携式的。也就是说,这可能是满足您的宏需求的最简单方法,因此我建议您探索这种可能性并准备在使用不同编译器时添加更多 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... 

};
于 2010-07-05T09:28:52.477 回答
0

如果你确定你使用的编译器会为此生成正确的代码(我想他们会,假设 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 引用等等。为简洁起见,我将它们省略了。)

于 2010-07-05T23:29:20.487 回答