Доступ к элементам типа 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]
.
- я хотел бы избежать написания
2 ответа:
Стандарт дает вам ответ на вопрос 1:
Существует широкий спектр вариантов ответа на вопрос 2. Если вам нравится нотация массива:9.2/15: нестатические данные члены класса с одинаковым управлением доступом распределяются таким образом, что более поздние члены имеют более высокие адреса внутри объекта класса. Порядок выделения нестатических данных члены с различным контролем доступа не указаны. реализация требования к выравниванию могут привести к тому, что два соседних элемента не будут распределяются сразу же друг за другом; так могли бы требования к пространство для управления виртуальными функциями и виртуальными базовыми классами.
- почему бы не использовать
switch()
, чтобы безопасно вернуть ссылку на правильный элемент.- или лучше, почему бы не заменить ваши члены реальным массивом ?
Первый будет выглядеть так:
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]; } };
Вы можете сделать это эффективно в соответствии со стандартами, используя статический массив указателей на члены.
Объем памяти составляет один массив на класс. Генерируемый код идентичен прямому доступу к элементу, когда индекс известен во время компиляции, в оптимизированных сборках.
Вот пример кода (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; }