方便的矢量3f类
convenient Vector3f class
有时需要一个Vector3f
类,它有x
、y
和z
成员,并且可以同时索引为float[3]
数组(SO已经有几个关于这个问题的问题)。
像这样:
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
的空间(这是非常不幸的。在这种特殊情况下,我看不出有什么理由不能删除引用,也许是编译器部分错过了优化)。
但是,有了[[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
的访问。这些属性不应占用空间,因为它们是空的,并且具有[[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
如果这将存在于标头中,并且您对编译器的优化功能有一定的信心,那么您可能会坚持使用普通的旧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;
}
}
};
我把它扔到编译器资源管理器(https://godbolt.org/z/0X4FPL)中,它显示了clang在-O2
和GCC在-O3
优化operator[]
调用。不如你的方法那么令人兴奋,但很简单,在大多数情况下应该有效。
但是这种实现很糟糕,因为引用在结构中占用了空间(这是非常不幸的。在这种特殊情况下,我看不出有什么理由不能删除引用,也许是编译器部分错过了优化)。
这看起来是一个复杂的问题。标准布局类必须彼此兼容。因此,编译器不允许消除任何成员,无论它们是如何定义的。对于非标准布局?谁知道呢。有关更多信息,请阅读以下内容:C++标准是否保证未使用的私有字段会影响大小?
根据我的经验,编译器从不删除类成员,即使它们是"未使用的"(例如,正式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;
}
...
};
你说这段代码很丑。也许是吧。但它很简单,易于阅读和维护。没有 UB,它不依赖于工会的潜在黑客,并且完全可以做你想做的事,除了美容要求。:)
GLM 在匿名union
中使用匿名struct
实现此类功能
我个人不能保证这是符合标准的,但大多数主要的编译器(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++标准,以我的方式访问非活动成员是有效的,因为这些成员共享一个"公共子序列":
如果两个联合成员是标准布局类型,则在任何编译器上检查其公共子序列都是明确定义的。
CPP参考:工会宣言
因为x
和data[0]
是
- 两者都
float
, - 两者都占用相同的内存,
- 都是标准定义的标准布局类型,
无论当前哪个处于活动状态,访问一个或另一个都是完全有效的。
如前所述,这是不可能的:指针算术仅在数组中定义,并且没有办法(如果不在类中放置引用,这在当前实现中占用空间)v.x
引用数组元素。
- 为了方便起见,我应该避免公开私有字段变量吗
- 方便地对C++中的所有字符串文字进行模糊处理
- 当使用Lua作为嵌入式语言(比如c++)时,有什么简单/方便的方法可以找到变量在Lua中的定义位置吗
- 使用typedef作为一种方便替换类名的方式是一种误用吗
- 如何在编译器方便的情况下在 C/C++ 中发布算术溢出异常
- 如果基类指针无法访问派生类成员函数,那么多态性有什么方便的呢?
- std::元组作为成员替换,方便宏
- 方便的矢量3f类
- 我是否需要这些 OpenGL 函数末尾的"3f"?
- 为什么C 没有方便的方式来为多维数组动态分配内存
- C++有没有方便的构造函数
- 方便的标志处理,所有标志都不能放入64位
- 有没有办法方便地为 Winsock send() 生成 HTTP 标头,而不是手动连接字符串
- 安全方便的通用散列(用于 STL 无序集和映射)习语?
- C++/V8调用非静态函数/创建临时实例的方便方法
- 有没有一种方便的方法可以从属性树中删除节点,同时保留其子节点
- 为具有一个数字数据成员的类定义所有比较运算符的方便方法
- 我可以方便地将QVariant转换回QList<MyType>吗?
- (定义宏以)方便 OpenGL 命令调试
- C++流 iomanip 等效于 sprintf "%5.3f"