像数组一样访问成员?
Accessing members like array?
由于阅读了很多警告不要像这样使用联合:
union rgba
{
struct
{
uint8_t r, g, b, a;
} components;
uint8_t index[4];
uint32_t value;
};
因为这是未定义的行为,所以我决定保持简单,如下所示:
struct rgba
{
uint8_t r, g, b, a;
};
但碰巧有时我确实需要使用索引在循环中访问 r
、g
、b
和 a
,否则我必须分别为每个组件复制相当长的代码。
所以我想到了这个:
struct rgba
{
u8 r, g, b, a;
constexpr u8& operator[](size_t x)
{
return const_cast<u8*>(&r)[x];
}
};
这依赖于以下假设:r
、g
、b
和 a
在内存中以线性方式放置,中间没有隐藏的样板,并且编译器保留变量顺序。
有了这个,我可以像我想要的那样访问组件:
rgba c;
for (int i = 0; i < 3; i++)
c[i] = i ^ (i + 7);
c.a = 0xFF;
- 因为我做了相当大的假设,所以我很确定这比使用联合进行类型双关更未定义的行为。我说得对吗?
- 我还能如何实现类似的设计?
- 我想尽可能避免写
c.r() = 5
,因为它看起来很有趣。
c.components[RED]
等访问器使用宏,我想避免使用宏。
- 考虑到访问此类枚举所需的命名空间,将第 2 点中的宏替换为枚举看起来很难看。想象一下
c.components[Image::Channels::Red]
.
标准给你关于问题1的答案:
9.2/15: Nonstatic data members of a class with the same access control are allocated so that later members have higher addresses
within a class object. The order of allocation of non-static data
members with different access control is unspecified. Implementation
alignment requirements might cause two adjacent members not to be
allocated immediately after each other; so might requirements for
space for managing virtual functions and virtual base classes.
问题 2 的答案有多种选择。如果您喜欢数组表示法:
- 为什么不使用
switch()
来安全地 return 引用正确的元素。
- 或更好,为什么不用真正的数组替换您的成员?
第一个看起来像:
struct rgba2
{
uint8_t r, g, b, a;
constexpr uint8_t& operator[](size_t x)
{
assert(x>=0 && x<4);
switch (x){
case 0: return r;
case 1: return g;
case 2: return b;
case 3: return a;
//default: throw(1); // not possible with constexpr
}
}
};
第二个:
struct rgba3
{
enum ergb { red, green, blue, alpha};
uint8_t c[alpha+1];
constexpr uint8_t& operator[](ergb x)
{
assert(x>=0 && x<4);
return c[x];
}
};
您可以使用指向成员的静态指针数组,以符合标准的方式高效地完成此操作。
内存开销是每个 class 一个数组。在优化构建中,当索引在编译时已知时,生成的代码与直接成员访问相同。
这是一些示例代码 (Source):
#include <iostream>
template<class T>
class Vector3
{
public:
Vector3(const T &xx, const T &yy, const T &zz) :
x(xx), y(yy), z(zz)
{
};
T& operator[](size_t idx)
{
return this->*offsets_[idx];
};
const T& operator[](size_t idx) const
{
return this->*offsets_[idx];
};
public:
T x,y,z;
private:
static T Vector3::* const offsets_[3];
};
template<class T>
T Vector3<T>::* const Vector3<T>::offsets_[3] =
{
&Vector3<T>::x,
&Vector3<T>::y,
&Vector3<T>::z
};
int main()
{
Vector3<float> vec(1,2,3);
vec[0] = 5;
std::cout << vec.x << std::endl;
}
由于阅读了很多警告不要像这样使用联合:
union rgba
{
struct
{
uint8_t r, g, b, a;
} components;
uint8_t index[4];
uint32_t value;
};
因为这是未定义的行为,所以我决定保持简单,如下所示:
struct rgba
{
uint8_t r, g, b, a;
};
但碰巧有时我确实需要使用索引在循环中访问 r
、g
、b
和 a
,否则我必须分别为每个组件复制相当长的代码。
所以我想到了这个:
struct rgba
{
u8 r, g, b, a;
constexpr u8& operator[](size_t x)
{
return const_cast<u8*>(&r)[x];
}
};
这依赖于以下假设:r
、g
、b
和 a
在内存中以线性方式放置,中间没有隐藏的样板,并且编译器保留变量顺序。
有了这个,我可以像我想要的那样访问组件:
rgba c;
for (int i = 0; i < 3; i++)
c[i] = i ^ (i + 7);
c.a = 0xFF;
- 因为我做了相当大的假设,所以我很确定这比使用联合进行类型双关更未定义的行为。我说得对吗?
- 我还能如何实现类似的设计?
- 我想尽可能避免写
c.r() = 5
,因为它看起来很有趣。 c.components[RED]
等访问器使用宏,我想避免使用宏。- 考虑到访问此类枚举所需的命名空间,将第 2 点中的宏替换为枚举看起来很难看。想象一下
c.components[Image::Channels::Red]
.
- 我想尽可能避免写
标准给你关于问题1的答案:
9.2/15: Nonstatic data members of a class with the same access control are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified. Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions and virtual base classes.
问题 2 的答案有多种选择。如果您喜欢数组表示法:
- 为什么不使用
switch()
来安全地 return 引用正确的元素。 - 或更好,为什么不用真正的数组替换您的成员?
第一个看起来像:
struct rgba2
{
uint8_t r, g, b, a;
constexpr uint8_t& operator[](size_t x)
{
assert(x>=0 && x<4);
switch (x){
case 0: return r;
case 1: return g;
case 2: return b;
case 3: return a;
//default: throw(1); // not possible with constexpr
}
}
};
第二个:
struct rgba3
{
enum ergb { red, green, blue, alpha};
uint8_t c[alpha+1];
constexpr uint8_t& operator[](ergb x)
{
assert(x>=0 && x<4);
return c[x];
}
};
您可以使用指向成员的静态指针数组,以符合标准的方式高效地完成此操作。
内存开销是每个 class 一个数组。在优化构建中,当索引在编译时已知时,生成的代码与直接成员访问相同。
这是一些示例代码 (Source):
#include <iostream>
template<class T>
class Vector3
{
public:
Vector3(const T &xx, const T &yy, const T &zz) :
x(xx), y(yy), z(zz)
{
};
T& operator[](size_t idx)
{
return this->*offsets_[idx];
};
const T& operator[](size_t idx) const
{
return this->*offsets_[idx];
};
public:
T x,y,z;
private:
static T Vector3::* const offsets_[3];
};
template<class T>
T Vector3<T>::* const Vector3<T>::offsets_[3] =
{
&Vector3<T>::x,
&Vector3<T>::y,
&Vector3<T>::z
};
int main()
{
Vector3<float> vec(1,2,3);
vec[0] = 5;
std::cout << vec.x << std::endl;
}