C++ 中的计算成员 Class 通过具有重载隐式转换的空结构成员
Computed Members in C++ Class by Empty Struct Members With Overloaded Implicit Conversions
在某些数据结构中,让成员的值在访问时从其他数据成员计算而不是存储会很有用。
例如,一个典型的 rect class 可能存储它的 left, top, right 和 bottom 成员数据字段中的坐标,并提供 getter 方法 return 根据这些值计算的 width 和 height,对于需要相对尺寸而不是绝对位置的客户。
struct rect
{
int left, top, right, bottom;
// ...
int get_width() const { return right - left; }
int get_height() const { return bottom - top; }
};
此实现允许我们获取和设置矩形边的绝对坐标,
float center_y = (float)(box.top + box.bottom) / 2.0;
此外,为了获得它的相对维度,尽管使用了稍微不同的方法调用运算符表达式语法:
float aspect = (float)box.get_width() / (float)box.get_height();
问题
然而,有人可能会争辩说,存储相对 width 和 height 而不是绝对 同样有效]right和bottom坐标,要求需要计算right和bottom[=59的客户端=] 值使用 getter 方法。
我的解决方案
为了避免需要记住哪种情况需要方法调用与数据成员访问运算符语法,我想出了一些在当前稳定的 gcc 中工作的代码和 clang 编译器。下面是 rect 数据结构的完整功能示例实现:
#include <iostream>
struct rect
{
union {
struct {
union { int l; int left; };
union { int t; int top; };
union { int r; int right; };
union { int b; int bot; int bottom; };
};
struct {
operator int() {
return ((rect*)this)->r - ((rect*)this)->l;
}
} w, width;
struct {
operator int() {
return ((rect*)this)->b - ((rect*)this)->t;
}
} h, height;
};
rect(): l(0), t(0), r(0), b(0) {}
rect(int _w, int _h): l(0), t(0), r(_w), b(_h) {}
rect(int _l, int _t, int _r, int _b): l(_l), t(_t), r(_r), b(_b) {}
template<class OStream> friend OStream& operator<<(OStream& out, const rect& ref)
{
return out << "rect(left=" << ref.l << ", top=" << ref.t << ", right=" << ref.r << ", bottom=" << ref.b << ")";
}
};
/// @brief Small test program showing that rect.w and rect.h behave like data members
int main()
{
rect t(3, 5, 103, 30);
std::cout << "sizeof(rect) is " << sizeof(rect) << std::endl;
std::cout << "t is " << t << std::endl;
std::cout << "t.w is " << t.w << std::endl;
std::cout << "t.h is " << t.h << std::endl;
return 0;
}
我在这里做的事情有什么错误吗?
关于嵌套空结构类型的隐式转换运算符中的指针转换,即这些行:
return ((rect*)this)->r - ((rect*)this)->l;
感觉很脏,好像我可能违反了良好的 C++ 风格约定。如果我的解决方案的这个或其他方面是错误的,我想知道推理是什么,最终,如果这是不好的做法那么是否有有效的方法达到相同的结果。
有一件事我通常希望能奏效:
auto w = t.w;
此外,以下行之一有效,另一行无效:
t.l += 3;
t.w += 3; // compile error
因此,您没有改变用户需要知道哪些成员是数据,哪些是函数的事实。
我只想让它们都起作用。反正封装性更好。我更喜欢全名,即左、上、下、右、宽度和长度。可能要多写几个字符,但大多数代码被阅读的次数比写的要多得多。额外的几个字符会得到回报。
在某些数据结构中,让成员的值在访问时从其他数据成员计算而不是存储会很有用。
例如,一个典型的 rect class 可能存储它的 left, top, right 和 bottom 成员数据字段中的坐标,并提供 getter 方法 return 根据这些值计算的 width 和 height,对于需要相对尺寸而不是绝对位置的客户。
struct rect
{
int left, top, right, bottom;
// ...
int get_width() const { return right - left; }
int get_height() const { return bottom - top; }
};
此实现允许我们获取和设置矩形边的绝对坐标,
float center_y = (float)(box.top + box.bottom) / 2.0;
此外,为了获得它的相对维度,尽管使用了稍微不同的方法调用运算符表达式语法:
float aspect = (float)box.get_width() / (float)box.get_height();
问题
然而,有人可能会争辩说,存储相对 width 和 height 而不是绝对 同样有效]right和bottom坐标,要求需要计算right和bottom[=59的客户端=] 值使用 getter 方法。
我的解决方案
为了避免需要记住哪种情况需要方法调用与数据成员访问运算符语法,我想出了一些在当前稳定的 gcc 中工作的代码和 clang 编译器。下面是 rect 数据结构的完整功能示例实现:
#include <iostream>
struct rect
{
union {
struct {
union { int l; int left; };
union { int t; int top; };
union { int r; int right; };
union { int b; int bot; int bottom; };
};
struct {
operator int() {
return ((rect*)this)->r - ((rect*)this)->l;
}
} w, width;
struct {
operator int() {
return ((rect*)this)->b - ((rect*)this)->t;
}
} h, height;
};
rect(): l(0), t(0), r(0), b(0) {}
rect(int _w, int _h): l(0), t(0), r(_w), b(_h) {}
rect(int _l, int _t, int _r, int _b): l(_l), t(_t), r(_r), b(_b) {}
template<class OStream> friend OStream& operator<<(OStream& out, const rect& ref)
{
return out << "rect(left=" << ref.l << ", top=" << ref.t << ", right=" << ref.r << ", bottom=" << ref.b << ")";
}
};
/// @brief Small test program showing that rect.w and rect.h behave like data members
int main()
{
rect t(3, 5, 103, 30);
std::cout << "sizeof(rect) is " << sizeof(rect) << std::endl;
std::cout << "t is " << t << std::endl;
std::cout << "t.w is " << t.w << std::endl;
std::cout << "t.h is " << t.h << std::endl;
return 0;
}
我在这里做的事情有什么错误吗?
关于嵌套空结构类型的隐式转换运算符中的指针转换,即这些行:
return ((rect*)this)->r - ((rect*)this)->l;
感觉很脏,好像我可能违反了良好的 C++ 风格约定。如果我的解决方案的这个或其他方面是错误的,我想知道推理是什么,最终,如果这是不好的做法那么是否有有效的方法达到相同的结果。
有一件事我通常希望能奏效:
auto w = t.w;
此外,以下行之一有效,另一行无效:
t.l += 3;
t.w += 3; // compile error
因此,您没有改变用户需要知道哪些成员是数据,哪些是函数的事实。
我只想让它们都起作用。反正封装性更好。我更喜欢全名,即左、上、下、右、宽度和长度。可能要多写几个字符,但大多数代码被阅读的次数比写的要多得多。额外的几个字符会得到回报。