C++ 为什么不是';破坏一个被新位置覆盖的对象不是一种未定义的行为吗?
我试图弄清楚以下行为是否是未定义的行为。我觉得这不是UB,但我对标准的解读让它看起来像是UB:C++ 为什么不是';破坏一个被新位置覆盖的对象不是一种未定义的行为吗?,c++,language-lawyer,undefined-behavior,object-lifetime,placement-new,C++,Language Lawyer,Undefined Behavior,Object Lifetime,Placement New,我试图弄清楚以下行为是否是未定义的行为。我觉得这不是UB,但我对标准的解读让它看起来像是UB: #include <iostream> struct A { A() { std::cout << "1"; } ~A() { std::cout << "2"; } }; int main() { A a; new (&a) A; } #包括 结构A{ 你误读了它 “析构函数是为构造对象隐式调用的”…意味着那些存在的对
#include <iostream>
struct A {
A() { std::cout << "1"; }
~A() { std::cout << "2"; }
};
int main() {
A a;
new (&a) A;
}
#包括
结构A{
你误读了它
“析构函数是为构造对象隐式调用的”…意味着那些存在的对象,它们的存在已经达到了完全构造的程度。虽然可以说没有完全解释,但原始的A
不符合此标准,因为它不再是“构造的”:它根本不存在!只有新的/替换对象会自动销毁,然后在main
的末尾,正如您所期望的那样
否则,这种新的布局形式将是非常危险的,在语言中具有争议的价值。然而,值得指出的是,以这种方式重新使用实际的A
,如果没有其他原因,只会导致这类问题的话,这有点奇怪和不寻常。通常情况下,您会将新的布局到一些平淡的缓冲区中(比如一个char[N]
或一些对齐的存储),然后自己调用析构函数
类似于您的示例的东西实际上可以在-上找到,它是UB,但这只是因为有人在B
上构造了T
;措辞非常清楚地表明,这是代码的唯一问题
但关键是:
本国际标准中赋予对象的属性仅在给定对象的使用寿命内适用。[..][]
评论太长了
Lightness的回答是正确的,他的链接是正确的参考
但让我们更精确地研究术语
- “存储持续时间”,关于内存
- “寿命”,关于物体
- “范围”,关于名称
对于自动变量,这三个变量都是一致的,这就是为什么我们常常不能清楚地区分“变量超出范围”。也就是说:名称超出范围;如果是具有自动存储持续时间的对象,则调用析构函数,结束命名对象的生存期;最后释放内存
在您的示例中,只有名称范围和存储持续时间重合-在其存在期间的任何一点上,名称a
表示有效内存-,对象生存期在相同内存位置的两个不同对象之间分割,且具有相同名称a
不,我认为您不能将11.3中的“构造”理解为“完全构造且未销毁”,因为如果对象的生存期被前面的显式析构函数调用提前结束,则会再次(错误地)调用dtor。
事实上,这是内存重用概念的一个问题:如果新对象的构造出现异常而失败,则会留下作用域,并尝试对不完整的对象或已删除的旧对象调用析构函数
我想你可以想象自动分配的类型化内存,标记为“被销毁”的标签,在堆栈解开时进行评估。C++运行时不真正跟踪单个对象或超出这个简单概念的状态。因为变量名称基本上是固定地址,所以很容易想到。“名称超出范围”触发对该位置假定存在的假定类型的命名对象的析构函数调用。如果其中一个假定是错误的,则所有赌注都无效
我是误读了标准,还是这实际上是未定义的行为
这些都没有。标准还不清楚,但可能更清楚。不过目的是调用新对象的析构函数,如[basic.life]第9页所示
[class.dtor]p12不太准确。我问了Core关于它的问题,然后:
我不会说这是一个矛盾[class.dtor]p12与[basic.life]p9],但确实需要澄清。析构函数的描述写得有点幼稚,没有考虑到占用自动存储位的原始对象可能已被占用相同自动存储位的另一个对象替换,但其目的是如果在该自动存储位上调用构造函数它需要自动存储来在其中创建一个对象——即,如果控制通过该声明流动——那么当块退出时,将为假定占用该自动存储位的对象调用析构函数——即使它不是构造函数调用创建的“同一”对象
我将在CWG问题发布后立即更新此答案。因此,您的代码没有UB。想象一下,使用placement new创建一个结构B
到a
对象所在的存储区。在作用域的末尾,将调用结构a
的析构函数(因为类型为a
的变量a
超出了范围),即使类型为B的对象现在真实地生活在那里
如前所述:
“如果程序以static结束T类型对象的生存期
([basic.stc.static])、线程([basic.stc.thread])或自动
([basic.stc.auto])存储持续时间,如果T具有非平凡的
析构函数,39程序必须确保原始
当隐式析构函数
呼叫发生;”
因此,在将B
放入a
存储区后,您需要销毁B
并再次将a
放入存储区,以避免违反上述规则。这在某种程度上并不直接适用于此处,因为您将a
放入a
,但它显示了行为。这表明,这种想法是错误的:
原来的析构函数是A