Доступ к членам структуры данных с помощью арифметики указателей - PullRequest
7 голосов
/ 17 октября 2019

Если у меня есть простой тензорный класс, подобный этому

struct Tensor
{
    double XX, XY, XZ;
    double YX, YY, YZ;
    double ZX, ZY, ZZ;
}

Является ли неопределенным поведение использовать арифметику указателя (см. Ниже) для доступа к его элементам?

 double& Tensor::operator[](int i) 
{ 
    assert(i < 9); 
    return (&XX)[i]; 
}

Ответы [ 4 ]

10 голосов
/ 17 октября 2019

Да, это неопределенное поведение.

Элементы данных не находятся в массиве, и, следовательно, НЕ гарантированно хранятся в непрерывной памяти, как того требует арифметика указателей. Между ними может быть сгенерировано неопределенное заполнение.

Правильным способом будет доступ к элементам по отдельности, например:

double& Tensor::operator[](int i)
{
    switch (i)
    {
        case 0: return XX;
        case 1: return XY;
        case 2: return XZ;
        case 3: return YX;
        case 4: return YY;
        case 5: return YZ;
        case 6: return ZX;
        case 7: return ZY;
        case 8: return ZZ;
        default: throw std::out_of_range("invalid index");
    }
}

В качестве альтернативы, если вы действительно хотите использовать синтаксис массива:

double& Tensor::operator[](int i)
{
    if ((i < 0) || (i > 8))
        throw std::out_of_range("invalid index");

    double* arr[] = {
        &XX, &XY, &XZ,
        &YX, &YY, &YZ, 
        &ZX, &ZY, &ZZ
    };

    return *(arr[i]);
}

Или

double& Tensor::operator[](int i)
{
    if ((i < 0) || (i > 8))
        throw std::out_of_range("invalid index");

    static double Tensor::* arr[] = {
        &Tensor::XX, &Tensor::XY, &Tensor::XZ,
        &Tensor::YX, &Tensor::YY, &Tensor::YZ, 
        &Tensor::ZX, &Tensor::ZY, &Tensor::ZZ
    };

    return this->*(arr[i]);
}

В противном случае используйте фактический массив для данных и определите методы для доступа к элементам:

struct Tensor
{
    double data[9];

    double& XX() { return data[0]; }
    double& XY() { return data[1]; }
    double& XZ() { return data[2]; }
    double& YX() { return data[3]; }
    double& YY() { return data[4]; }
    double& YZ() { return data[5]; }
    double& ZX() { return data[6]; }
    double& ZY() { return data[7]; }
    double& ZZ() { return data[8]; }

    double& operator[](int i)
    {
        if ((i < 0) || (i > 8))
            throw std::out_of_range("invalid index");
        return data[i];
    }
};
7 голосов
/ 17 октября 2019

Есть разговор cppcon, который упоминает об этом!

Так что да, это неопределенное поведение, потому что классы и массивы не имеют общей начальной последовательности.

Редактировать: Миро Кнейп представляет этот слайд около 3:44, если вы хотите больше контекста для всех не-c ++ на слайде, но вопрос и ответ - действительно единственная часть беседы, которая касается вашего вопроса.

3 голосов
/ 17 октября 2019

Еще одно возможное решение: обернуть ссылки в классе и получить массив оберток.

struct Tensor
{
    double XX, XY, XZ;
    double YX, YY, YZ;
    double ZX, ZY, ZZ;

    class DoubleRefenceWrapper
    {
        double & ref;
    public:
        DoubleRefenceWrapper(double & r) : ref(r) {}
        double & get() { return ref; }
    } elements[9];

    Tensor() : elements{XX, XY, XZ, YX, YY, YZ, ZX, ZY, ZZ} {}

    double& operator[](unsigned int i)
    {
        if(i < 9)
        {
            return elements[i].get();
        }
        throw std::out_of_range("Tensor index must be in [0-8] range");
    }
};
3 голосов
/ 17 октября 2019

Это неопределенное поведение.

Как правило, арифметика указателей правильно определяется только для элементов массива (и, возможно, одного элемента после, как описано в разделе §8.5.6 стандарта ).

Для классов / структур это не может работать, потому что компилятор может добавить заполнение или другие данные между членами. cppreference содержит краткое описание макета класса.

Теперь, переходя к решению вашей проблемы, первым делом будет просто использовать что-то, сделанное для этого, например Eigen . Это зрелая библиотека для линейной алгебры, с хорошо протестированным кодом и хорошими оптимизациями.

Если вы не заинтересованы в добавлении новой библиотеки, вам придется более или менее реализовать вручную либо доступ к элементу, либо operator[].

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...