方便的 Vector3f class
convenient Vector3f class
有时需要有一个Vector3f
class,它有x
、y
和z
成员,可以索引为同时一个 float[3]
数组(这里已经有几个关于这个的问题)。
类似于:
struct Vector3f {
float data[3];
float &x = data[0];
float &y = data[1];
float &z = data[2];
};
有了这个,我们可以这样写:
Vector3f v;
v.x = 2.0f;
v.y = 3.0f;
v.z = 4.0f;
glVertex3fv(v.data);
但是这个实现很糟糕,因为引用在 struct
中使用 space(这很不幸。我看不出在这种特殊情况下不能删除引用的任何原因,也许它错过了编译器部分的优化。
但是,有了 [[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);
所以,基本上,我在 struct
中有属性,它处理对 x
、y
和 z
的访问。这些属性不应采用 space,因为它们是空的,并且具有 [[no_unique_address]]
的属性
您如何看待这种做法?有UB吗?
注意,这个问题是关于一个 class,所有这些都是可能的:
Vector3f v;
v.x = 1;
float tmp = v.x;
float *c = v.<something>; // there, c points to a float[3] array
GLM 在匿名 union
中使用匿名 struct
s 来实现这种功能
我个人不能保证这符合标准,但大多数主要编译器(MSVC、GCC、Clang)都会支持这个习惯用法:
struct Vector3f {
union {
struct {
float x, y, z;
};
struct {
float data[3];
};
};
Vector3f() : Vector3f(0,0,0) {}
Vector3f(float x, float y, float z) : x(x), y(y), z(z) {}
};
int main() {
Vector3f vec;
vec.x = 14.5;
std::cout << vec.data[0] << std::endl; //Should print 14.5
vec.y = -22.345;
std::cout << vec.data[1] << std::endl; //Should print -22.345
std::cout << sizeof(vec) << std::endl; //On most platforms will print 12
}
非标准行为存在于用于将字母组合在一起的匿名结构中,GCC 将对此发出警告。据我所知,union
本身应该是有效的,因为数据类型都是相同的,但是如果您不确定这是否有效,您仍然应该检查您的编译器文档。
为了更加方便,我们还可以重载括号运算符来稍微缩短我们的语法:
struct Vector3f {
/*...*/
float& operator[](size_t index) {return data[index];}
float operator[](size_t index) const {return data[index];}
};
int main() {
Vector3f vec;
vec.x = 14.5;
std::cout << vec[0] << std::endl; //Should print 14.5
vec.y = -22.345;
std::cout << vec[1] << std::endl; //Should print -22.345
std::cout << sizeof(vec) << std::endl; //On most platforms will print 12
}
为了清楚起见,根据 C++ 标准,以我的方式访问非活动成员是有效的,因为这些成员共享一个“公共子序列”:
If two union members are standard-layout types, it's well-defined to examine their common subsequence on any compiler.
因为x
和data[0]
是
- 两者都
float
,
- 两者占用相同的内存,
- 都是标准布局类型,正如标准定义的那样,
访问一个或另一个是完全有效的,无论哪个当前处于活动状态。
如果这将在 header 中存在,并且您对编译器的优化能力有一定的信心,您可以坚持使用 plain-old operator[]()
重载并期待编译器足够聪明,可以省略调用和 return 您想要的元素。例如:
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;
}
}
};
我将其放入编译器资源管理器 (https://godbolt.org/z/0X4FPL),它显示 clang 优化了 -O2
处的 operator[]
调用,以及 -O3
处的 GCC。不如您的方法令人兴奋,但简单并且在大多数情况下应该有效。
如前所述,这是不可能的:指针算法仅在数组中定义,而且没有办法(如果不在 class 中放置引用,这在当前实现中占用 space)让 v.x
引用数组元素。
But this implementation is bad, because references take space in the struct (which is quite unfortunate. I don't see any reason why references cannot be removed in this particular case, maybe it is missed optimization from the compiler's part).
这看起来是个复杂的问题。标准布局 classes 必须相互兼容。因此,编译器不允许删除任何成员,无论它们是如何定义的。对于非标准布局?谁知道。欲了解更多信息,请阅读:Do the C++ standards guarantee that unused private fields will influence sizeof?
根据我的经验,编译器永远不会删除 class 成员,即使它们是 "unused"(例如 sizeof
正式使用它们)。
Does it have UB?
我认为这是UB。首先[[no_unique_address]]
只是表示会员不需要有唯一地址,并不是说一定不能有唯一地址。其次,不清楚您的 data
成员从哪里开始。同样,编译器可以自由使用或不使用先前 [[no_unique_address]]
class 成员的填充。这意味着您的访问者可能会访问不正确的内存。
另一个问题是你想从"inner"class访问"outer"内存。 AFAIK 这样的东西在 C++ 中也是 UB。
What do you think about this approach?
假设它是正确的(事实并非如此)我仍然不喜欢它。你想要 getters/setters 但 C++ 不支持此功能。因此,与其做那些奇怪的、复杂的构造(想象其他人维护这段代码)不如简单地做
struct Vector3f {
float data[3];
float x() {
return data[0];
}
void x(float value) {
data[0] = value;
}
...
};
你说这段代码很丑。也许是。但它很简单,易于阅读和维护。没有 UB,它不依赖于与工会的潜在黑客攻击,并且完全按照你的意愿行事,除了美观要求。 :)
有时需要有一个Vector3f
class,它有x
、y
和z
成员,可以索引为同时一个 float[3]
数组(这里已经有几个关于这个的问题)。
类似于:
struct Vector3f {
float data[3];
float &x = data[0];
float &y = data[1];
float &z = data[2];
};
有了这个,我们可以这样写:
Vector3f v;
v.x = 2.0f;
v.y = 3.0f;
v.z = 4.0f;
glVertex3fv(v.data);
但是这个实现很糟糕,因为引用在 struct
中使用 space(这很不幸。我看不出在这种特殊情况下不能删除引用的任何原因,也许它错过了编译器部分的优化。
但是,有了 [[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);
所以,基本上,我在 struct
中有属性,它处理对 x
、y
和 z
的访问。这些属性不应采用 space,因为它们是空的,并且具有 [[no_unique_address]]
您如何看待这种做法?有UB吗?
注意,这个问题是关于一个 class,所有这些都是可能的:
Vector3f v;
v.x = 1;
float tmp = v.x;
float *c = v.<something>; // there, c points to a float[3] array
GLM 在匿名 union
中使用匿名 struct
s 来实现这种功能
我个人不能保证这符合标准,但大多数主要编译器(MSVC、GCC、Clang)都会支持这个习惯用法:
struct Vector3f {
union {
struct {
float x, y, z;
};
struct {
float data[3];
};
};
Vector3f() : Vector3f(0,0,0) {}
Vector3f(float x, float y, float z) : x(x), y(y), z(z) {}
};
int main() {
Vector3f vec;
vec.x = 14.5;
std::cout << vec.data[0] << std::endl; //Should print 14.5
vec.y = -22.345;
std::cout << vec.data[1] << std::endl; //Should print -22.345
std::cout << sizeof(vec) << std::endl; //On most platforms will print 12
}
非标准行为存在于用于将字母组合在一起的匿名结构中,GCC 将对此发出警告。据我所知,union
本身应该是有效的,因为数据类型都是相同的,但是如果您不确定这是否有效,您仍然应该检查您的编译器文档。
为了更加方便,我们还可以重载括号运算符来稍微缩短我们的语法:
struct Vector3f {
/*...*/
float& operator[](size_t index) {return data[index];}
float operator[](size_t index) const {return data[index];}
};
int main() {
Vector3f vec;
vec.x = 14.5;
std::cout << vec[0] << std::endl; //Should print 14.5
vec.y = -22.345;
std::cout << vec[1] << std::endl; //Should print -22.345
std::cout << sizeof(vec) << std::endl; //On most platforms will print 12
}
为了清楚起见,根据 C++ 标准,以我的方式访问非活动成员是有效的,因为这些成员共享一个“公共子序列”:
If two union members are standard-layout types, it's well-defined to examine their common subsequence on any compiler.
因为x
和data[0]
是
- 两者都
float
, - 两者占用相同的内存,
- 都是标准布局类型,正如标准定义的那样,
访问一个或另一个是完全有效的,无论哪个当前处于活动状态。
如果这将在 header 中存在,并且您对编译器的优化能力有一定的信心,您可以坚持使用 plain-old operator[]()
重载并期待编译器足够聪明,可以省略调用和 return 您想要的元素。例如:
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;
}
}
};
我将其放入编译器资源管理器 (https://godbolt.org/z/0X4FPL),它显示 clang 优化了 -O2
处的 operator[]
调用,以及 -O3
处的 GCC。不如您的方法令人兴奋,但简单并且在大多数情况下应该有效。
如前所述,这是不可能的:指针算法仅在数组中定义,而且没有办法(如果不在 class 中放置引用,这在当前实现中占用 space)让 v.x
引用数组元素。
But this implementation is bad, because references take space in the struct (which is quite unfortunate. I don't see any reason why references cannot be removed in this particular case, maybe it is missed optimization from the compiler's part).
这看起来是个复杂的问题。标准布局 classes 必须相互兼容。因此,编译器不允许删除任何成员,无论它们是如何定义的。对于非标准布局?谁知道。欲了解更多信息,请阅读:Do the C++ standards guarantee that unused private fields will influence sizeof?
根据我的经验,编译器永远不会删除 class 成员,即使它们是 "unused"(例如 sizeof
正式使用它们)。
Does it have UB?
我认为这是UB。首先[[no_unique_address]]
只是表示会员不需要有唯一地址,并不是说一定不能有唯一地址。其次,不清楚您的 data
成员从哪里开始。同样,编译器可以自由使用或不使用先前 [[no_unique_address]]
class 成员的填充。这意味着您的访问者可能会访问不正确的内存。
另一个问题是你想从"inner"class访问"outer"内存。 AFAIK 这样的东西在 C++ 中也是 UB。
What do you think about this approach?
假设它是正确的(事实并非如此)我仍然不喜欢它。你想要 getters/setters 但 C++ 不支持此功能。因此,与其做那些奇怪的、复杂的构造(想象其他人维护这段代码)不如简单地做
struct Vector3f {
float data[3];
float x() {
return data[0];
}
void x(float value) {
data[0] = value;
}
...
};
你说这段代码很丑。也许是。但它很简单,易于阅读和维护。没有 UB,它不依赖于与工会的潜在黑客攻击,并且完全按照你的意愿行事,除了美观要求。 :)