C++ псевдонимы переменных-членов?
Я уверен, что это возможно, потому что я уверен, что видел, как это было сделано. Я думаю, что это круто, но я с удовольствием приму ответы в духе "это ужасная идея, потому что ____".
Скажем, у нас есть базовая структура.
struct vertex
{
float x, y, z;
};
Теперь я хочу реализовать псевдонимы для этих переменных.
vertex pos;
vertex col;
vertex arr;
pos.x = 0.0f; pos.y = 0.5f; pos.z = 1.0f;
col.r = 0.0f; col.g = 0.5f; col.b = 1.0f;
arr[0] = 0.0f; arr[1] = 0.5f; arr[2] = 1.0f;
В идеале третий синтаксис был бы неотличим от массива. То есть если бы я отправил arr
в качестве ссылочного параметра для функции, ожидающей массив чисел с плавающей запятой, в которые она будет хранить данные (например, многие из OpenGL glGet
функции), это будет работать нормально.
Как вы думаете? Возможный? Возможно, но глупо?
13 ответов
Что я хотел бы сделать, это сделать аксессоры:
struct Vertex {
float& r() { return values[0]; }
float& g() { return values[1]; }
float& b() { return values[2]; }
float& x() { return values[0]; }
float& y() { return values[1]; }
float& z() { return values[2]; }
float operator [] (unsigned i) const { return this->values_[i]; }
float& operator [] (unsigned i) { return this->values_[i]; }
operator float*() const { return this->values_; }
private:
float[3] values_;
}
Безымянные вложенные структуры в объединении не являются стандартом C++. Это, однако, должно работать:
struct Vertex
{
private:
typedef float Vertex::* const vert[3];
static const vert v;
public:
typedef size_t size_type;
float x, y, z;
const float& operator[](size_type i) const {
return this->*v[i];
}
float& operator[](size_type i) {
return this->*v[i];
}
};
const Vertex::vert Vertex::v = {&Vertex::x, &Vertex::y, &Vertex::z};
РЕДАКТИРОВАТЬ: немного больше информации. Структура использует массив из трех элементов указателя на данные для доступа к данным в перегруженных операторах [].
Строка "typedef float Vertex::* const vert" означает, что vert является указателем на член с плавающей точкой структуры Vertex. [3] означает, что это массив из 3 из них. В перегруженном операторе [] этот массив индексируется, а указатель на элемент данных разыменовывается и возвращается значение.
Кроме того, этот метод должен работать независимо от проблем с упаковкой - компилятор может дополнить структуру Vertex, как ему угодно, и все равно будет работать нормально. У анонимного союза возникнут проблемы, если поплавки будут упакованы по-разному.
Использовать союз?
union vertex
{
struct { float x, y, z; };
struct { float r, g, b; };
float arr[3];
};
Я бы не рекомендовал это - это приведет к путанице.
Добавлено:
Как отметил Адриан в своем ответе, этот союз с анонимными членами структуры не поддерживается ISO C++. Он работает в GNU G++ (с жалобами на отсутствие поддержки при включении-Wall -ansi -pedantic
"). Это напоминает предстандартные дни C (до K&R 1st Edn), когда имена элементов структуры должны были быть уникальными во всех структурах, и вы могли использовать сокращенные обозначения, чтобы получить смещение внутри структуры, и вы могли использовать имена членов из других типов структур - форма анархии. К тому времени, когда я начал использовать C (давным-давно, но после K&R1), это было уже историческое использование.
Обозначения, показанные с анонимными членами объединения (для двух структур), поддерживаются C11 (ISO/IEC 9899:2011), но не более ранними версиями стандарта C. Раздел 9.5 ISO/IEC 14882:2011 (C++11) предусматривает анонимные союзы, но GNU g++
(4.9.1) не принимает код, указанный с -pedantic
идентифицируяwarning: ISO C++ prohibits anonymous structs [-Wpedantic]
".
Поскольку идея приведет к путанице, меня не особенно беспокоит, что она не стандартная; Я бы не использовал механизм для этой задачи (и я бы опасался использовать анонимные структуры в объединении, даже если это было бы выгодно).
Была высказана обеспокоенность:
Три (xyz, rgb и массив) не обязательно выровнены.
Это союз с тремя элементами; три элемента начинаются с одного адреса. Первые два являются структурами, содержащими 3 значения с плавающей точкой. Нет наследования и нет виртуальных функций для предоставления разных макетов и т. Д. Структуры будут расположены с тремя смежными элементами (на практике, даже если стандарт допускает заполнение). Массив также начинается с того же адреса, и при условии отсутствия заполнения в структурах элементы перекрывают две структуры. Я действительно не вижу, что будет проблема.
Рекомендации?
template<typename T>
struct vertex {
vertex() :
r(data[0]), g(data[1]), b(data[2]),
x(data[0]), y(data[1]), z(data[2])
{
}
T *operator *() {
return data;
}
const T *operator *() const {
return data;
}
T data[3];
T &r, &g, &b;
T &x, &y, &z;
};
Следующая структура будет иметь запрошенное поведение:
struct vertex
{
private:
float data[3];
public:
float &x, &y, &z;
float &r, &g, &b;
vertex() : x(data[0]), y(data[1]), z(data[2]), r(data[0]), g(data[1]), b(data[2]) {
}
float& operator [](int i) {
return data[i];
}
};
Вы можете получить это с профсоюзом, как уже упоминали другие. Перегрузка цвета и положения на одну и ту же структуру может быть не очень хорошей идеей (например, добавление двух цветов обычно означает, что вы хотите насытить до 1.0, тогда как добавление векторов происходит линейно), но наложение плавающего [] поверх них, как это совершенно нормально и хорошо принятый способ обмена данными с GL/DirectX/ и т. д.
Однако я рекомендую избегать обращения к одному и тому же члену разными псевдонимами в одной и той же области действия, потому что это приведет вас к неприятному аппаратному сбою, который называется load-hit-store. В частности, избегайте этого, если можете:
vector foo;
foo.x = 1.0f;
return foo[0] + foo[1];
Я полагаю, вы можете использовать некоторую макро-магию, чтобы получить желаемое Но это будет выглядеть ужасно. Почему вы хотите использовать одну и ту же структуру, вершину для 3 разных типов? Почему вы не можете определить класс для цвета? Также имейте в виду, что вершина и цвет не совпадают. Если вы измените что-то на вершину, это также повлияет на цвет, если у вас будет один класс для обоих.
Я не уверен, правильно ли я понял вопрос. Но похоже, что вам нужно перегрузить оператор [], чтобы предоставить массиву доступ к вашей структуре / классу. Смотрите пример, упомянутый здесь: Перегрузка оператора
Вы можете попробовать добавить ссылки на переменные, например так:
struct test {
float x, y, z;
float &r, &g, &b;
test() : r(x), g(y), b(z) {}
};
Но ваша структура становится больше (от 12 байтов до 40 байтов).
Чтобы использовать [] для него, используйте перегрузку оператора [], как упоминалось ранее.
На мой взгляд, плохая идея, по крайней мере, для приведенного примера: недостатком является то, что практически для любого решения этой проблемы вы, вероятно, сможете свободно назначать экземпляры "rgb" / из экземпляров "xyz", что вероятно, редко разумно или правильно. то есть вы рискуете отказаться от некоторого полезного типа безопасности.
Лично для примера, который я привожу, я бы создал подклассы типов rgb и xyz из базы boost::array<float,3>
или похожие. Таким образом, оба они наследуют operator[], могут быть переданы функциям, ожидающим массивы, и переданы с большей безопасностью типов вещам, ожидающим цвета / координаты. Часто вы хотите рассматривать xyz или rgb как массив, но редко вы хотите рассматривать xyz как rgb или наоборот. (rgb IS-A массив: ОК. xyz IS-A массив: ОК. rgb IS-A xyz???? Я так не думаю!)
Конечно, это означает, что доступ к x,y,z & r,g,b должен быть предоставлен аксессором (переадресация на соответствующий operator[](...)
), а не прямо на члена. (Для этого вам понадобятся свойства C#).
У меня есть шаблон и два векторных класса ниже, один сумасшедший, один вменяемый. Шаблон реализует простой фиксированный во время компиляции массив значений. Он предназначен для создания подклассов и использует переменную защищенного массива, чтобы вам не приходилось прыгать через обручи для доступа к массиву. (Некоторым людям может не понравиться такой дизайн. Я говорю, если ваши подклассы вызывают ваши перегруженные операторы, связывание может быть хорошей идеей.)
Сумасшедший класс позволяет вам иметь переменные-члены с именами x, y, z, и он действует как массив для вызовов glGetFloatV. У нормального только есть функции доступа x(), y(), z() и все еще работает с glGetFloatV. Вы можете использовать любой класс в качестве основы для других векторных объектов, которые вы можете передать в библиотеку OpenGL. Хотя приведенные ниже классы относятся только к точкам, очевидно, вы можете просто выполнить поиск / замену, чтобы превратить их в цветовые классы rgb.
Сумасшедший класс сумасшедший, потому что стоимость синтаксического сахара vec.x вместо vec.x() равна 3 ссылочным переменным. Это может занять много места в большом приложении. Используйте более простую вменяемую версию.
template <typename T, int N>
class FixedVector {
protected:
T arr[N];
public:
FixedVector();
FixedVector(const T* a) {
for (int i = 0; i < N; ++i) {
arr[i] = a[i];
}
}
FixedVector(const T& other) {
for (int i = 0; i < N; ++i) {
arr[i] = other.arr[i];
}
}
FixedVector& operator=(const T& other) {
for (int i = 0; i < N; ++i) {
arr[i] = other.arr[i];
}
return *this;
}
T* operator&() { return arr; }
const T* operator&() const { return arr; }
T& operator[](int ofs) {
assert(ofs >= 0 && ofs < N);
return arr[ofs];
}
const T& operator[](int ofs) const {
assert(ofs >= 0 && ofs < N);
return arr[ofs];
}
};
class CrazyPoint : public FixedVector<float, 3> {
public:
float &x, &y, &z;
CrazyPoint()
: x(arr[0]), y(arr[1]), z(arr[2])
{ arr[0] = arr[1] = arr[2] = 0.0; }
CrazyPoint(const float* a)
: x(arr[0]), y(arr[1]), z(arr[2])
{
arr[0] = a[0];
arr[1] = a[1];
arr[2] = a[2];
}
CrazyPoint(float a, float b, float c)
: x(a), y(b), z(c)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
}
};
class SanePoint : public FixedVector<float, 3> {
public:
float& x() { return arr[0]; }
float& y() { return arr[1]; }
float& z() { return arr[2]; }
SanePoint() { arr[0] = arr[1] = arr[2] = 0.0; }
SanePoint(float a, float b, float c)
{
arr[0] = a;
arr[1] = b;
arr[2] = c;
}
};
// usage
SanePoint normal;
glGetFloatV(GL_CURRENT_NORMAL, &normal);
Я думаю, что постер искал что-то очень простое, без накладных расходов на производительность - как вы хотели бы с чем-то вроде 3D-векторного класса. Таким образом, добавление виртуальных функций (затраты на обращение к vtable), дополнительных членов (затраты памяти), союзов (каждое новое имя может потребовать перекомпиляции всех пользователей) или даже магии препроцессора (увеличение размера программы, эквивалентность кросс-типа) нежелательно.
В реальном мире можно было бы взять шаблонный класс Vector3 (который может быть основан на двойном или плавающем значении) и применить его к другим сценариям в удобной для пользователя форме. Скорее всего, он определяется членами [x, y, z], но если вы хотите использовать его для вращений, вам может понадобиться [psi, theta, phi], для скоростей [dx, dy, dz] и т. д.
Для всего типа вы можете использовать следующие псевдонимы во время компиляции:using Rotation3 = Vector3;
Но кажется, что нет ничего более простого и эффективного, что вы можете сделать для псевдонимов базовых переменных, верно?
Просто предупреждение об использовании ссылочных членов, указывающих на значения членов. Вам необходимо определить конструктор копирования (и, возможно, также оператор присваивания), если вы когда-либо копируете такой объект (например, передаете его по значению). Конструктор копирования по умолчанию предоставит вам копию, чьи ссылочные элементы указывают на значения элементов исходного объекта, а не на элементы нового объекта. Это, конечно, не то, что вы хотите.
Учитывая, что вы также получите более крупные объекты, как уже указывалось, я думаю, что использование методов доступа должно быть предпочтительнее, чем ссылочные элементы.