(适用5.3.4)
新表达
:: opt _ new new-placement_ opt new-type-id new-initializeropt
:: opt _ new new-placement_ opt (type-id)new-initializeropt
由new-expression创建的实体具有动态存储持续时间 (3.7.4)。 [注意:这种实体的生命周期不一定 限于创建它的范围。 - 结束说明]
我认为以下内容有1个主要对象( local_object ),自动存储持续时间,以及3个具有动态存储持续时间的虚拟类。 / p>
struct dummy
{
int a;
};
char local_object[256];
dummy * a = new(&local_object) dummy;
dummy * b = new(&local_object +100) dummy;
dummy * c = new(&local_object +200) dummy;
用户@ M.M.认为只有一个对象(local_object),其余的只是指针。这是对的吗?
(3.7)
动态存储时间与使用 operator new
创建的对象相关联
答案 0 :(得分:8)
在我看来,标准(在OP中引用)只能在读取时被解释,即运算符new创建动态存储持续时间的对象,即使为自动持续时间的对象获取了底层内存。
§3.8[basic.life]第8段中的标准预期了这种精确的情况,并参考了以下未定义行为的例子:
class T { };
struct B {
~B();
};
void h() {
B b;
new (&b) T;
}
该段写着:
如果程序以静态(3.7.1),线程(3.7.2)或自动(3.7.3)存储持续时间结束类型为T的对象的生命周期,并且如果T具有非平凡的析构函数,则程序必须确保在进行隐式析构函数调用时,原始类型的对象占用相同的存储位置;否则程序的行为是不确定的。
在该示例中,程序通过重用其存储来“终止对象b
的生命周期”,如同一部分的第4段所示:(强调添加)。
程序可以通过重用对象占用的存储或通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期。
在示例代码中,b
的析构函数未被调用,但这是可以接受的,因为段落4明确允许不调用非平凡的析构函数:
对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数;
只要程序准备好接受析构函数未被调用的后果。
但是要回到第8段,b
的生命周期已经结束,并且存储已被重用以创建T
类型的对象。此对象具有动态存储持续时间,这意味着不会隐式调用其析构函数。如上所述,只要程序不需要可能由析构函数执行的任何副作用,也不必明确调用析构函数。
尽管b
的生命周期已经结束,b
具有自动存储持续时间这一事实意味着当控制流离开其范围时,将隐式调用其析构函数。在生命周期结束的对象上调用析构函数是禁止使用其生命周期已终止的对象的特定情况,如§3.8的第6段所述,该段禁止调用其生命周期为的对象的非静态成员函数。已结束但其存储尚未重复使用或已发布。
因此,示例程序的行为未定义。
但本节第7段为程序提供了一种机制,通过在同一位置重新创建相同类型的不同对象来避免未定义的行为:
如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,则在原始对象占用的存储位置创建新对象,指向原始对象的指针,引用原始对象的引用,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以用于操作新对象,如果:
(7.1) - 新对象的存储完全覆盖原始对象占用的存储位置,
(7.2) - 新对象与原始对象的类型相同(忽略顶级cv限定符),
(7.3) - 原始对象的类型不是const限定的,如果是类类型,则不包含任何类型为const限定或引用类型的非静态数据成员,并且
(7.4) - 原始对象是类型为T的派生程度最高的对象(1.8),新对象是类型为T的派生程度最高的对象(也就是说,它们不是基类子对象)。
因此,在我的解释中,以下代码段将定义行为:
class T { };
struct B {
~B();
};
void h() {
B b;
new (&b) T;
new (&b) B; /* recreate a B so that it can be destructed */
}
简而言之,该标准考虑了使用分配给自动存储持续时间的对象的内存可以创建动态存储持续时间的对象的可能性,并为执行此操作的定义良好的程序提供一组限制和要求。从而避免了通过重用其存储来对生命周期结束的对象执行隐式析构函数的后果。