我(重新?)发明了这种使用数据成员语法实现零成本属性的方法。我的意思是用户可以写:
some_struct.some_member = var;
var = some_struct.some_member;
并且这些成员访问以零开销重定向到成员函数。
虽然初步测试表明该方法在实践中确实有效,但我还不确定它是否不存在未定义的行为。下面是说明该方法的简化代码:
template <class Owner, class Type, Type& (Owner::*accessor)()>
struct property {
operator Type&() {
Owner* optr = reinterpret_cast<Owner*>(this);
return (optr->*accessor)();
}
Type& operator= (const Type& t) {
Owner* optr = reinterpret_cast<Owner*>(this);
return (optr->*accessor)() = t;
}
};
union Point
{
int& get_x() { return xy[0]; }
int& get_y() { return xy[1]; }
std::array<int, 2> xy;
property<Point, int, &Point::get_x> x;
property<Point, int, &Point::get_y> y;
};
测试驱动程序证明该方法有效,并且确实是零成本(属性不占用额外的内存):
int main()
{
Point m;
m.x = 42;
m.y = -1;
std::cout << m.xy[0] << " " << m.xy[1] << "\n";
std::cout << sizeof(m) << " " << sizeof(m.x) << "\n";
}
真正的代码有点复杂,但方法的要点就在这里。它基于使用真实数据的并集(xy
在此示例中)和空属性对象。 (真实数据必须是标准布局类才能正常工作)。
需要联合,因为否则属性尽管是空的,但会不必要地占用内存。
为什么我觉得这里没有UB?该标准允许访问标准布局联合成员的公共初始序列。这里,公共初始序列为空。数据成员x
and y
根本不被访问,因为没有数据成员。我对标准的阅读表明这是允许的。reinterpret_cast
应该没问题,因为我们正在将一个联合成员强制转换为其包含的联合,并且这些是指针可相互转换的。
标准确实允许这样做,还是我在这里遗漏了一些 UB?