有时,需要有一个Vector3f类,它有x,y和z会员,并可以被索引的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,它处理的访问x,y和z。这些属性不应为空格,因为它们为空,并且具有[[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)
如果这将存在于标头中,并且您对编译器的优化功能有一定的信心,则可以坚持旧的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。不如您的方法令人兴奋,但简单,在大多数情况下都可以使用。
但这个实现很糟糕,因为引用占用了结构中的空间(这是非常不幸的。我看不出在这种特殊情况下不能删除引用的任何原因,也许是编译器部分错过了优化)。
这看起来是一个复杂的问题。标准布局类必须相互兼容。因此编译器不允许消除任何成员,无论它们是如何定义的。对于非标准布局?谁知道。有关详细信息,请阅读以下内容: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,它不依赖于工会的潜在黑客行为,并且完全按照您的意愿行事,除了美观要求。:)