像这样的 C++ 中的“重新绑定”引用合法吗?

Is "rebinding" references in C++ like this legal?

提问人:user541686 提问时间:1/14/2014 最后编辑:templatetypedefuser541686 更新时间:1/16/2014 访问量:2069

问:

以下内容在 C++ 中合法吗?

据我所知,有一个微不足道的析构函数,所以它应该是合法的。
但我认为参考资料不能合法地反弹......他们可以吗?
Reference

template<class T>
struct Reference
{
    T &r;
    Reference(T &r) : r(r) { }
};

int main()
{
    int x = 5, y = 6;
    Reference<int> r(x);
    new (&r) Reference<int>(y);
}
C++ 参考 语言-律师 安置-新

评论

5赞 Fred Larson 1/14/2014
不是你的反对者,但我猜这是对做这种事情的恐怖的下意识反应。不过,这是一个有趣的问题。
3赞 1/14/2014
在这些情况下,反对票应该被 mod 或社区经理撤消。这是不合理的。
1赞 user541686 1/14/2014
也许我根本不应该提到析构函数的琐碎性——我只是意识到我完全可以在放置之前完成——新,所以析构函数是否微不足道并不真正影响这个问题......r.~Reference<int>()
0赞 chris 1/14/2014
@Mehrdad,但它确实引起了一些有趣的讨论,我觉得非常好。

答:

18赞 Mark Ransom 1/14/2014 #1

您不是在重新绑定参照,而是在另一个对象的内存中创建一个新对象,并放置一个新对象。由于从未运行过旧对象的析构函数,我认为这将是未定义的行为。

评论

3赞 chris 1/14/2014
您能详细说明一下未运行的析构函数吗?如果是微不足道的,那么它不运行真的是UB吗?
5赞 CB Bailey 1/14/2014
如果析构函数在重新运行对象的存储之前未运行,则不会自动运行 UB,只有“任何依赖于析构函数产生的副作用的程序都有未定义的行为”(ISO/IEC 14882:2011 3.8 [basic.life] / 4),这为整个类程序提供了省略析构函数调用的可能性 - 即使是非平凡的程序 - 并且没有 UB。
0赞 user541686 1/14/2014
是的,我认为你错过了问题的重点,@CharlesBailey一针见血。事实上,我不知道如果我们不依赖它们的效果,可以随意省略非平凡的析构函数......
1赞 Mark Ransom 1/14/2014
@CharlesBailey,感谢您的参考 - 引用的析构函数是否保证没有副作用?我想不出会有什么情况,但我的想象力以前让我失望了。
2赞 CB Bailey 1/14/2014
我不认为引用甚至没有析构函数。
11赞 CB Bailey 1/14/2014 #2

在您的示例中没有提到反弹。第一个引用(构造在第二行,名称为 )在其整个生命周期内都绑定到 by 表示。当第三行的放置 new 表达式重用其包含对象的存储时,此引用的生存期将结束。替换对象包含一个引用,该引用在其整个生存期内受到约束,一直持续到其作用域的末尾 - 结束。r.rintxymain

评论

1赞 user541686 1/14/2014
我并没有那么着迷于术语(“重新绑定”),而是对实际代码如此着迷......所以代码是合法的/定义明确的?
0赞 CB Bailey 1/14/2014
代码没有错,末尾没有 UB,因为 Reference 有一个微不足道的析构函数。即使没有,仍然不会有 UB,因为您确保在发生隐式析构函数调用的点(即 的末尾)的存储中存在正确类型的对象。mainrmain
1赞 Mark Ransom 1/14/2014
优化编译器难道不能得出结论,既然引用在该上下文中是已知的,并且不能被反弹并且没有被破坏,那么它可以直接使用底层源代码并完全错过重建吗?
0赞 user541686 1/14/2014
@MarkRansom我喜欢这一点,这是我没有想到的一个很好的观点。让问题更有趣!
2赞 CB Bailey 1/14/2014
@MarkRansom:但是“尚未被销毁的部分”是一个不正确的假设,因为将一个对象的存储重新用于另一个对象会破坏原始对象。这意味着这种优化是不合格的。
4赞 dyp 1/16/2014 #3

我想我在“引用”下面的一段话中找到了答案,该段落谈到了琐碎的 dtor / dtor 副作用,即 [basic.life]/7:

如果在对象的生存期结束后,在重新使用或释放该对象所占用的存储之前,则在原始对象占用的存储位置创建一个新对象,指向原始对象的指针、引用原始对象的引用或原始对象的名称将自动引用新对象,并且, 新对象的生存期开始后,可以 用于操作新对象,如果:

  • 新对象的存储完全覆盖了原始对象占用的存储位置,并且

  • 新对象与原始对象的类型相同(忽略顶级 cv 限定符),并且

  • 原始对象的类型不是 const 限定的,如果是类类型,则不包含其类型为 const 限定的任何非静态数据成员或引用类型,并且

  • 原始对象是 Type 最派生的对象,而 New 对象是 Type 的派生对象(即,它们不是基类子对象)。TT

通过重用存储,我们结束了原始对象的生存期 [basic.life]/1

类型对象的生存期在以下情况下结束:T

  • 如果是具有非平凡析构函数的类类型,则析构函数调用将启动,或者T

  • 对象占用的存储被重用或释放。

所以我认为 [basic.life]/7 涵盖了这种情况

Reference<int> r(x);
new (&r) Reference<int>(y);

其中,我们结束用 表示的对象的生存期,并在同一位置创建一个新对象。r

与具有引用数据成员的类类型一样,满足 [basic.life]/7 的要求。也就是说,甚至可能不引用新对象,我们可能不会使用它来“操作”这个新创建的对象(我也将此“操作”解释为只读访问)。Reference<int>r

评论

0赞 dyp 1/16/2014
嗯,虽然我首先阅读了生命周期结束的条件,要么你调用 dtor,要么你重用存储,但我现在认为这是一个严格的分离:要么它有一个不平凡的 dtor 并且你调用它,要么你重用存储[basic.life]/7 中的例子并不是很有帮助,因为 dtor 无论如何都是微不足道的(你在这里,@Mehrdad?
0赞 user541686 1/16/2014
是的,第 1 段似乎在两者之间严格区分,我最初没有看到它,所以我认为只有当有一个不平凡的 dtor(来自另一段)时,一生才能结束。答案是有道理的,而且可能是正确的......让我再考虑一下,但我可能会接受它,谢谢!+1
0赞 alfC 6/22/2018
@dyp,那么,这是合法的吗?
0赞 dyp 7/13/2018
@alfC 我有点脱节,但据我所知,由于引用成员,使用该名称访问该对象过去和现在都是非法的。如今,人们可以使用 std::launder 来解决一些问题。r