我已经(再次?)发明了这种方法与数据成员的语法零成本的特性。我的意思是,用户可以写:
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
和y
的数据成员没有访问所有的,因为没有数据成员。我的标准的读数表明,这是允许的。 reinterpret_cast
应该是可以的,因为我们有铸造工会成员包含它的工会,而这些都是指针互相转换。
难道这确实是由标准允许的,或者我在这里缺少一些UB?
TL; DR这是UB。
同样,一个对象的生命周期之前已经开始,但是对象将占用已分配或对象的生命周期结束后贮存后其占用的对象被重用或释放的存储,是指任何glvalue前原来的对象可以被使用,但仅在有限的方式。在建或破坏的对象,请参见[class.cdtor]。否则,这样的glvalue是指分配的存储,并使用不依赖于它的值glvalue的特性是良好定义的。该计划不确定的行为,如果:[...]
- 所述glvalue用于调用所述对象的非静态成员函数,或
通过definition,一个工会的非活动成员不在其一生中。
一个可能的解决方法是使用C ++ 20 [[no_unique_address]]
struct Point
{
int& get_x() { return xy[0]; }
int& get_y() { return xy[1]; }
[[no_unique_address]] property<Point, int, &Point::get_x> x;
[[no_unique_address]] property<Point, int, &Point::get_y> y;
std::array<int, 2> xy;
};
static_assert(offsetof(Point, x) == 0 && offsetof(Point, y) == 0);
这里是什么common-initial-sequence rule says about unions:
在具有结构类型
T1
的积极成员标准布局联合,允许读取结构类型m
的另一联合成员的非静态数据成员T2
提供m为T1和T2的公共初始序列的一部分;的行为是因为如果T1的相应构件被提名。
您的代码不符合。为什么?因为你不从“另一个工会会员”阅读。你正在做m.x = 42;
。这就是不读;是的呼唤另一个工会会员的成员函数。
因此,它不符合公共初始序列规则。而如果没有共初始序列规则来保护你,访问工会的非活跃会员为UB。