方便的Vector3f类

gez*_*eza 9 c++ c++20

有时,需要有一个Vector3f类,它有xyz会员,并可以被索引的float[3]同时阵列(这里还有几个问题在SO已经这个)。

就像是:

struct Vector3f {
    float data[3];
    float &x = data[0];
    float &y = data[1];
    float &z = data[2];
};
Run Code Online (Sandbox Code Playgroud)

这样,我们可以这样写:

Vector3f v;
v.x = 2.0f;
v.y = 3.0f;
v.z = 4.0f;
glVertex3fv(v.data);
Run Code Online (Sandbox Code Playgroud)

但是这种实现是不好的,因为引用占用了空间struct(这是很不幸的。我看不到在这种特殊情况下无法删除引用的任何原因,也许编译器方面没有进行优化)。

但是,有了[[no_unique_address]]我这个主意:

#include <new>

template <int INDEX>
class Vector3fProperty {
    public:
        operator float() const {
            return propertyValue();
        }
        float &operator=(float value) {
            float &v = propertyValue();
            v = value;
            return v;
        }
    private:
        float &propertyValue() {
            return std::launder(reinterpret_cast<float*>(this))[INDEX];
        }
        float propertyValue() const {
            return std::launder(reinterpret_cast<const float*>(this))[INDEX];
        }
};

struct Vector3f {
    [[no_unique_address]]
    Vector3fProperty<0> x;
    [[no_unique_address]]
    Vector3fProperty<1> y;
    [[no_unique_address]]
    Vector3fProperty<2> z;

    float data[3];
};

static_assert(sizeof(Vector3f)==12);
Run Code Online (Sandbox Code Playgroud)

所以,基本上,我在性能struct,它处理的访问xyz。这些属性不应为空格,因为它们为空,并且具有[[no_unique_address]]

您如何看待这种方法?有UB吗?


注意,这个问题是关于一个类的,所有这些都是可能的:

Vector3f v;
v.x = 1;
float tmp = v.x;
float *c = v.<something>; // there, c points to a float[3] array
Run Code Online (Sandbox Code Playgroud)

you*_*mit 5

如果这将存在于标头中,并且您对编译器的优化功能有一定的信心,则可以坚持旧的operator[]()重载,并期望编译器足够聪明,可以取消调用并返回所需的元素。例如:

class Vec3f {
public:
    float x;
    float y;
    float z;

    float &operator[](int i) {
        if(i == 0) {
            return x;
        }
        if(i == 1) {
            return y;
        }
        if(i == 2) {
            return z;
        }
    }
};
Run Code Online (Sandbox Code Playgroud)

我把它扔进了Compiler Explorer(https://godbolt.org/z/0X4FPL),它显示了clang优化了operator[]调用-O2,而GCC 优化了-O3。不如您的方法令人兴奋,但简单,在大多数情况下都可以使用。


fre*_*ish 4

但这个实现很糟糕,因为引用占用了结构中的空间(这是非常不幸的。我看不出在这种特殊情况下不能删除引用的任何原因,也许是编译器部分错过了优化)。

这看起来是一个复杂的问题。标准布局类必须相互兼容。因此编译器不允许消除任何成员,无论它们是如何定义的。对于非标准布局?谁知道。有关详细信息,请阅读以下内容:Do the C++standardsguaranteed private fields willfluence sizeof?

根据我的经验,编译器永远不会删除类成员,即使它们“未使用”(例如,正式sizeof使用它们)。

有UB吗?

我认为这就是UB。首先仅仅[[no_unique_address]]意味着成员不需要有唯一的地址,并不是说它一定不能有唯一的地址。其次,不清楚你的data会员从哪里开始。同样,编译器可以自由地使用或不使用先前[[no_unique_address]]类成员的填充。这意味着您的访问器可能会访问不正确的内存。

另一个问题是您想从“内部”类访问“外部”内存。AFAIK这样的东西在C++中也是UB。

您对这种方法有何看法?

假设它是正确的(事实并非如此)我仍然不喜欢它。您需要 getter/setter,但 C++ 不支持此功能。因此,与其做那些奇怪的、复杂的构造(想象一下其他人维护这段代码)不如简单地做

struct Vector3f {
    float data[3];
    float x() {
        return data[0];
    }
    void x(float value) {
        data[0] = value;
    }
    ...
};
Run Code Online (Sandbox Code Playgroud)

你说这段代码很难看。也许是的。但它很简单,易于阅读和维护。没有 UB,它不依赖于工会的潜在黑客行为,并且完全按照您的意愿行事,除了美观要求。:)