通过指针算法访问结构数据成员

时间:2019-10-17 08:51:20

标签: c++ undefined-behavior pointer-arithmetic

如果我有一个像这样的简单张量类

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 个答案:

答案 0 :(得分:10)

是的,这是未定义的行为。

数据成员不在数组中,因此不能保证如指针算法所要求的那样将它们背对背存储在连续存储器中。它们之间可能会产生不确定的填充。

正确的方法是分别访问成员,例如:

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];
    }
};

答案 1 :(得分:7)

There's a cppcon talk that mentions this!

是的,这是不确定的行为,因为类和数组不共享公共的初始序列。

编辑:Miro Knejp在幻灯片3:44左右介绍了该幻灯片,如果您想要幻灯片上所有非c ++的更多背景信息,但问题和答案实际上是涉及到该问题的唯一部分。 / p>

答案 2 :(得分:3)

这是未定义的行为。

通常,仅针对数组的成员(以及standard第8.5.6节中所述)可能为数组的成员正确定义了指针算法。

对于类/结构,此无效,因为编译器可以在成员之间添加填充或其他数据。 cppreference简要描述了类布局。

现在,转到解决问题的方法,第一个方法就是简单地使用为此制作的内容,例如Eigen。它是线性代数的成熟库,具有经过良好测试的代码和良好的优化。

如果您对添加新库不感兴趣,则必须或多或少手动实现成员访问或operator[]

答案 3 :(得分:3)

另一种可能的解决方案:将引用包装在一个类中并具有包装器数组。

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");
    }
};