关于C++标准[basic.life]/10透明替换规则的疑问:联合体成员切换的合法性探讨
先说明一下,这是个language-lawyer标签下的问题,咱们聚焦的是C++标准的规则措辞,不是实际开发中的实用技巧(不过如果有针对这个问题的实用建议,也很欢迎)。
首先看[basic.life]/10里关于“透明替换”的措辞:
before the storage which the object occupied is reused
我对这个规则的适用场景有个困惑,先看下面的代码片段:
int i = 0; new (&i) float(3.5); // 复用了i的存储 new (&i) int(5); // 这里[basic.life]/10的透明替换规则是否不适用? int j = i + 1; // 结果是6?还是未定义行为?
按照[basic.life]/2的最后一部分和示例1的说明,placement new一般会被认定为“存储复用”。那上面第三行的placement new,是不是因为没有发生在i的存储被复用之前(毕竟第二行已经把i的存储用来放float了),所以[basic.life]/10的透明替换规则不适用?那之后访问i就是未定义行为?
再来看联合体的场景,这也是我困惑的核心点:难道切换联合体的活跃成员没有通用的安全方式吗?
union U{int i; float f;}; U u = {5}; // i是活跃成员 // 选项1: u.f = 2.5; // 赋值会隐式创建联合体成员的对象 u.i = 7; // 选项2: new(&u.f) float(2.5); new(&u.i) int(7); // 之后能不能安全访问u.i?
先分析选项1:这种赋值的方式只有当涉及的类型是平凡类型或者内置类型时,才会隐式创建对象;如果是非平凡类型,标准明确要求必须用placement new,所以这不是通用的解决方案。而且就算咱们假设选项1不算[basic.life]/10里的“存储复用”,它的适用范围也太窄了。
选项2的问题就和之前的代码片段一样:第二次用placement new创建int的时候,之前已经用placement new创建了float,相当于已经复用了存储,那[basic.life]/10的规则是不是就不适用了?那之后访问u.i是不是不安全?
这就很矛盾了——按照这个理解,咱们根本没有通用的安全方式切换联合体的活跃成员,这显然不符合设计联合体的初衷啊。所以我想问:是我对[basic.life]/10的理解错了?还是标准本身存在缺陷?另外还有个小疑问:联合体里&u.f和&u.i的地址相同,但指针类型不同,会不会因为指针类型的差异,就不被认定为“存储复用”?
内容来源于stack exchange

