C++ Не виртуальная переменная члена класса макет памяти?

У меня есть не виртуальный шаблон класса A как показано ниже, и я делаю следующее

#include <iostream>
// my class template
template<typename T>
class A
{
    public:
    T x;
    T y;
    T z;
    // bunch of other non-virtual member functions including constructors, etc
    // and obviously no user-defined destructor
    // ...
};

int main()
{
    //now I do the following
    A<double> a;
    a.x = 1.0; // not important this
    a.y = 2.0;
    a.z = 3.0;

    // now the concerned thing 
    double* ap = (double*)&a;
    double* xp = &(a.x);

    // can I correctly and meaningfully do the following?     
    double new_az = ap[2]; // guaranteed to be same as a.z (for any z) ? ** look here **
    double new_z = xp[2]; // guaranteed to be same as a.z (for any z) ? ** look here **

    std::cout<<new_az<<std::endl;
    std::cout<<new_z<<std::endl;
    return 0;
}

Итак, гарантируется ли, что если я использую необработанную точку для объекта A или к переменной-члену a.xЯ правильно получу другие переменные?

3 ответа

Как отмечали многие пользователи, нет никакой гарантии, что структура памяти вашей структуры будет идентична соответствующему массиву. И "идеологически правильный" способ доступа к членам по индексу создаст некрасивый operator [] с switch внутри него.

Однако, на практике, с вашим подходом проблем обычно не возникает, и предлагаемые решения уступают с точки зрения генерируемого кода и производительности во время выполнения.

Я могу предложить 2 других решения.

  1. Сохраните свое решение, но во время компиляции убедитесь, что структура вашей структуры соответствует массиву. В вашем конкретном случае выкладываю STATIC_ASSERT(sizeof(a) == sizeof(double)*3);
  2. Измените ваш класс шаблона, чтобы он был фактически массивом, и преобразуйте x,y,zпеременные в функции доступа в элементы массива.

Я имею в виду:

#include <iostream>
// my class template
template<typename T>
class A
{
public:
    T m_Array[3];

    T& x() { return m_Array[0]; }
    const T& x() const { return m_Array[0]; }

    // repeat for y,z
    // ...
};

Если вы сделаете длину массива (т. Е. Размерность представляемого вектора) также параметром шаблона, вы можете поместить 'STATIC_ASSERT' в каждую функцию доступа, чтобы обеспечить фактическое существование члена.

Нет, нет гарантии, не так, как вы это делаете. Например, если T - int8_t, он будет работать, только если вы указали 1-байтовую упаковку.

Самый простой и правильный способ сделать это - добавить оператор [] в класс шаблона, например:

T& operator[](size_t i)
{
  switch(i)
  {
  case 0: return x;
  case 1: return y;
  case 2: return z:
  }
  throw std::out_of_range(__FUNCTION__);
}

const T& operator[](size_t i) const
{
  return (*const_cast<A*>(this))[i];  // not everyone likes to do this.
}

Но это не очень эффективно. Более эффективный способ - иметь ваши векторные (или точечные) координаты в массиве и функции-члены x(), y(), z() для доступа к ним. Тогда ваш пример будет работать во всех случаях, если вы реализуете оператор T* в своем классе.

operator T*() { return &values[0]; }
operator const T*()const  { return &values[0]; }

Если вы действительно хотите делать такие вещи:

template <typename T>
class FieldIteratable
{
  using Data = std::array<T, 5/*magic number*/>;
  Data data_;
  public:
  const Data & data() { return data_; }
  T& a1 = data_[0]; // or some macro
  char padding1[3]; // you can choose what field is iteratable
  T& a2 = data_[1];
  char padding2[3]; // class can contain other fields can be
  T& a3 = data_[2];
  char padding3[3];
  T& a4 = data_[3];
  char padding4[3];
  T& a5 = data_[4];


};



int main() {

  FieldIteratable<int> fi;

  int* a = &fi.a1;
  *a++ = 0;
  *a++ = 1;
  *a++ = 2;
  *a++ = 3;
  *a++ = 4;

  std::cout << fi.a1 << std::endl;
  std::cout << fi.a2 << std::endl;
  std::cout << fi.a3 << std::endl;
  std::cout << fi.a4 << std::endl;
  std::cout << fi.a5 << std::endl;

  for(auto i :fi.data())
    std::cout << i << std::endl;

  return 0;
}
Другие вопросы по тегам