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

发布于 2024-09-08 08:37:55 字数 1674 浏览 3 评论 0原文

假设有一个类型 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]
但它需要额外的查找表,我试图避免。

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

//编辑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% 的。我问我们是否可以期望某些编译器会在统一字段之间放置非统一填充。

Let have a type T and a struct having ONLY uniform elements of T type.

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

I'd like to access them in fallowing way:

struct Foo {
    T one,
    T two,
    T three

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

where cpp_offsetof macro (it is considered to be correct) is:

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

The C++ standard doesn't guarantee it, but can we assume that members are distanced by a fixed offset and above is correct, cross-platform solution?

100% compatible solution would be:

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]standard, compliant and faster version was presented by snk_kid using pointers to data members[/edit]
but it requires extra lookup table which I'm trying to avoid.

//EDIT
And one more. I cannot use just an array and constants to index these fields, they have to be named fields of a struct (some macro requires that).

//EDIT2
Why those have to be named fields of a struct? What is the macro? It is settings system of a bigger project. Simplifying it's sth like this:

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)
};

And once again: I'm not looking form 100% compatible solution but 99%. I'm asking if we can expect that some compilers will put non-uniform padding between uniform fields.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

謌踐踏愛綪 2024-09-15 08:37:55

您的代码不适用于非 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 与指向数据成员的指针表一起使用获得优化。请查看此处了解我在说什么。

Your code doesn't work with NON-POD types such those which using virtual member functions. There is a standard compliant (and efficient) way to achieve what you're trying to do, using pointer to data members:

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 };

Just make sure you use const every with the table of pointer to data-members to get optimizations. Check here to see what I'm talking about.

﹉夏雨初晴づ 2024-09-15 08:37:55

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

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

Another way is with template specialization if what you are trying to achieve is still a compile time feature.

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;}

It would be nice to define get as a member function but you cannot
specialize template member functions. Now if this is only a compile time
expansion you are looking for then this will avoid the lookup table
issue of one of the previous posts. If you need runtime resolution
then you need a lookup table obviously.

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

守望孤独 2024-09-15 08:37:55

您也许能够使用数组来保存数据(这样您就可以在不使用查找表的情况下获得索引访问)并引用各种数组元素(这样您就可以拥有“命名”元素以供您使用)来实现您想要的目标宏)。

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

You might be able to achieve what you want using an array to hold the data (so you can get indexed access without using a lookup table) and having references to the various array elements (so you can have 'named' elements for use by your macros).

I'm not sure what your macros require, so I'm not 100% sure this will work, but it might. Also, I'm not sure that the slight overhead of the lookup table approach is worth jumping through too many hoops to avoid. On the other hand, I don't think the approach I suggest here is any more complex than the table-of-pointers approach, so here it is for your consideration:

#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;
}
小伙你站住 2024-09-15 08:37:55

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

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

第一种是使用特定于编译器的关键字或编译指示宏,这将强制编译器不添加填充字节。但这是不可移植的。
也就是说,这可能是满足您的宏要求的最简单方法,因此我建议您探索这种可能性,并准备在使用不同编译器时添加更多编译指示。

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

struct Foo {

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


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

};

You can't because the compiler can add dead bytes between members to allow padding.

There is two ways to do what you want.

The first is to use your compiler-specific keyword or pragma macro that will force the compiler to not add padding bytes. But that is not portable.
That said it might be the easiest way to do it with your macro requirements, so I suggest you explore this possibility and prepare for adding more pragma when using different compilers.

The other way is to first make sure your members are aligned, then add accessors :

struct Foo {

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


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

};
可是我不能没有你 2024-09-15 08:37:55

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

If you're sure the compilers you're using are going to generate the right code for this (and I'd imagine they would, assuming T isn't a reference type anyway) the best thing to do is put in some kind of check that the struct is laid out as you think. I can't think of any particular reason to insert non-uniform padding between adjacent members of the same type, but if you check the struct layout by hand then you'll at least know if it happens.

If the struct (S) has exactly N members of type T, for example, you can check at compile time that they are tightly packed simply using sizeof:

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

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

If this compiles, then they're tightly packed, as there's no space for anything else.

If you just happen to have N members, that you want to ensure are placed directly one after the other, you can do something similar using 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];

Depending on the compiler, this might have to become a runtime check, possibly not using offsetof -- which you might want to do for non-POD types anyway, because offsetof isn't defined for them.

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

This doesn't say anything about what to do if the asserts start failing, but you should at least get some warning that the assumptions aren't true...

(By the way, insert appropriate casts to char references and so on where appropriate. I left them out for brevity.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文