Запрос о объявлениях переменных класса в C ++ - PullRequest
5 голосов
/ 08 июня 2011

У меня есть класс для представления трехмерного вектора с плавающей точкой:

class Vector3D
{
    public:

    float x, y, z;
    float * const data;

    Vector3D() : x(0.0), y(0.0), z(0.0), data(&x) {}
}

Мой вопрос: будут ли x, y и z последовательно выделяться в памяти, чтобы я мог назначить адресx к данным, а затем использовать оператор индекса для доступа к компонентам вектора в виде массива?

Например, иногда мне может потребоваться прямой доступ к компонентам вектора:

Vector3D vec;
vec.x = 42.0;
vec.y = 42.0;
vec.z = 42.0;

Ииногда я могу получить к ним доступ по смещению:

Vector3D vec;
for (int i = 3; i--; )
    vec.data[i] = 42.0;

Будет ли второй пример иметь такой же эффект, как и первый, или я рискую перезаписать память, отличную от x, y и zпоплавки

Ответы [ 6 ]

6 голосов
/ 08 июня 2011

Нет, это неопределенное поведение по двум причинам:

  • Во-первых, о проблемах с заполнением, о которых упоминали все остальные.
  • Во-вторых, даже если вещи заполнены правильно, недопустимо разыменовывать указатель со смещением, которое выводило бы его за пределы того, на что он указывает. Компилятор может принять это и оптимизировать, что приведет к неопределенному поведению, если вы нарушите его.

Однако, допустимо следующее:

class Vector3D
{
public:
    std::array<float,3> data;
    float &x, &y, &z;

    Vector3D() : data(), x(data[0]), y(data[1]), z(data[2]) { }
    Vector3D& operator =(Vector3D const& rhs) { data = rhs.data; return *this; }
};

std::array является новым для C ++ 0x и в основном эквивалентно boost::array. Если вам не нужен C ++ 0x или Boost, вы можете использовать std::vector (и изменить инициализатор на data(3)), хотя это гораздо более тяжелое решение, его размер можно изменить из внешнего мира, и если это так, то результатом будет UB.

2 голосов
/ 08 июня 2011

Да.Этот класс является совместимым с макетом стандартным макетом , поскольку:

  • У вас нет виртуальных функций.
  • Все элементы данных находятся в одном блоке спецификатора доступа (public:)

Благодаря этому гарантируется, что он будет последовательно размечен, как структура C.Это то, что позволяет вам читать и записывать заголовки файлов как структуры.

1 голос
/ 08 июня 2011

Ваше решение недопустимо, но если вы можете гарантировать (или знать), что ваш компилятор будет "делать правильные вещи" (в частности, управляя заполнением между элементами x, y и z), у вас все будет в порядке. В этом случае я бы вообще удалил элемент data и использовал operator[].

Я видел что-то подобное, которое иногда использовали. Он сталкивается с точно такими же проблемами, но экономит ваше хранение указателя данных и допускает более приятный синтаксис v[0], чем v.data[0].

class Vector3D
{
    public:

    float x, y, z;
    float& operator[](int i) { return *(&x+i); }
    const float& operator[](int i) const { return *(&x+i); }

    Vector3D() : x(0.0), y(0.0), z(0.0) {}
}

РЕДАКТИРОВАТЬ: По подсказке ildjam вот совместимая версия с использованием аксессоров, а не членов, что аналогично.

class Vector3D
{
    public:
      float& operator[](int i) { return v[i]; }
      const float& operator[](int i) const { return v[i]; }

      float& x() { return v[0]; }
      float  x() const { return v[0]; }
      float& y() { return v[1]; }
      float  y() const { return v[1]; }
      float& z() { return v[2]; }
      float  z() const { return v[2]; }

      Vector3D() : v() {}
    private:    
      float v[3];
};
1 голос
/ 08 июня 2011

или вы можете иметь перегрузку оператора []

float operator[](int idx)
{
 switch (idx)
{
case 0:
  return x;
case 1:
  return y;
case 2:
 return z;
}
assert (false);
}
1 голос
/ 08 июня 2011

Компилятор обладает некоторой гибкостью в распределении памяти в структуре.Структура никогда не будет перекрывать другую структуру данных, но она может внедрить неиспользуемое пространство между элементами.В предоставленной вами структуре некоторые компиляторы могут добавить 4 байта дополнительного пространства между z и данными, чтобы указатель данных мог быть выровнен.Большинство компиляторов предоставляют способ упаковки все плотно.

EDIT : нет гарантии, что компилятор выберет упаковку x, y и z плотно, но вна практике они будут хорошо упакованы, потому что они являются первыми элементами структуры и потому, что они имеют степень двойки.

0 голосов
/ 08 июня 2011

Сделайте что-то вроде этого:

float data[3];
float& x, y, z;

    Vector3D() : x(data[0]), y (data[1]), z(data[2]) { data [0] = data [1] = data [2] = 0;}
...