C++ 类中由具有重载隐式转换的空结构成员计算的成员
Posted
技术标签:
【中文标题】C++ 类中由具有重载隐式转换的空结构成员计算的成员【英文标题】:Computed Members in C++ Class by Empty Struct Members With Overloaded Implicit Conversions 【发布时间】:2016-05-19 00:26:57 【问题描述】:在某些数据结构中,让成员的值在访问时从其他数据成员计算而不是存储是很有用的。
例如,一个典型的 rect 类可能会存储它的 left、top、right 和 成员数据字段中的底部坐标,并提供 getter 方法,根据这些值为客户端返回计算得到的 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 值的客户端使用 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++ 风格约定。如果我的解决方案的这个或其他方面有问题,我想知道原因是什么,最终,如果这是不好的做法,那么是否有有效的 获得相同结果的方法。
【问题讨论】:
【参考方案1】:我通常期望工作的一件事却没有:
auto w = t.w;
此外,以下几行之一有效,另一行无效:
t.l += 3;
t.w += 3; // compile error
因此,您并没有改变用户需要知道哪些成员是数据,哪些是函数这一事实。
我只是让它们都起作用。无论如何,它是更好的封装。我更喜欢全名,即左、上、下、右、宽度和长度。可能要多写几个字符,但大多数代码的阅读频率要比编写频率高得多。额外的几个字符将得到回报。
【讨论】:
还有一个问题是它会根据标准调用未定义的行为(尽管大多数编译器都做了正确的事情,有些明确地提供了更强的保证) 但是第二个问题,特别是t.w += 3; // compile error
无法编译...因为分配r 的宽度没有意义。问问自己,如果它确实编译了,你希望这条线做什么?移动左边还是右边?还是应该移动两个相等的量以保持矩形的中点?由于意图不明确,因此不允许该操作,编译器将其标记为错误是正确的。
@VictorCondino 实际上,我只是想指出您并没有摆脱您的类的用户需要区分数据成员和非数据成员的事实。就这样。我的建议不变:让用户通过函数访问一切。当然,t.width() += 3 那时没有意义,但 t.left() += 3 也没有意义。这将是一致的。一致性有很长的路要走。
我所指的 UB 是联合的有效类型是最后一个被赋值的成员,并且在技术上不允许访问任何其他成员。
@Rumburak t.width() += 3
如果返回代理对象可能是有效的,但我同意这是一个糟糕的设计选择。以上是关于C++ 类中由具有重载隐式转换的空结构成员计算的成员的主要内容,如果未能解决你的问题,请参考以下文章