考虑这个代码(不同的值renew
和cleanse
):
struct T {
int mem;
T() { }
~T() { mem = 42; }
};
// identity functions,
// but breaks any connexion between input and output
int &cleanse_ref(int &r) {
int *volatile pv = &r; // could also use cin/cout here
return *pv;
}
void foo () {
T t;
int &ref = t.mem;
int &ref2 = cleanse ? cleanse_ref(ref) : ref;
t.~T();
if (renew)
new (&t) T;
assert(ref2 == 42);
exit(0);
}
是assert
保证通过?
据我所知, 不建议采用这种风格。 像“这不是一个正确的做法” 意见是不准备在这里。
我想显示来自标准报价一个完整的逻辑证明一个答案。 编译器作者的意见也可能是有趣的。
编辑:现在在一个两个问题! 请参阅renew
参数(与renew == 0
,这是原来的问题)。
编辑2:我想我的问题其实是:什么是一个成员对象?
编辑3:现在与其他cleanse
参数!
我第一次有这两个报价,但现在我觉得他们其实只是指定之类的东西int &ref = t.mem;
的一生中一定会发生t
。 这是这样,在你的榜样。
12.7条第1款:
对于具有非平凡的析构函数的对象,参考任何非静态成员或基类中的析构函数之后的物体的表面涂饰在未定义的行为的执行结果。
第3款:
为了形成一个指针(或访问的值)的物体的直接非静态成员obj
,建设obj
应当已经开始和它的破坏不得已完成时,指针值的其他方式的计算(或访问构件值)将导致不确定的行为。
我们这里有类型的完整的对象T
和类型的成员子对象int
。
3.8第1款:
类型的对象的生存期T
开始时:
- 与类型的适当对准和尺寸存储
T
获得,和 - 如果对象具有不平凡的初始化,初始化完成。
类型的对象的生存期T
结束时:
- 如果
T
是具有一个非平凡的析构函数(12.4)的类类型,析构函数呼叫开始时,或 - 该对象占据存储被重复使用或释放。
顺便说一句,3.7.3 P1:
这些[自动存储持续时间]实体存储持续直到它们被创建离开块。
和3.7.5:
构件子对象,基类的子对象和数组元素的存储持续时间是它们的完整的对象(1.8)的。
因此,没有关于编译器的“释放”的前存储的忧虑exit
在这个例子中。
在3.8p2非规范的笔记中提到,“12.6.2描述基地和成员子对象的生命周期,”但那里的语言只有大约初始化和析构函数,而不是“存储”或“终生”的谈判,所以我的结论是部分不不影响“寿命”为琐碎类型的子对象的定义。
如果我解释这一切的权利,当renew
是假的,完整的类对象的生命周期在明确的析构函数调用结束时结束,但寿命int
子对象持续到节目结束。
3.8第5和第6说,指针和引用之前或任何对象的生命周期可以在有限的方式使用后,“分配存储”,并列出了一大堆的东西,你可能不会与他们无关。 左值到右值的转换,如表达ref == 42
要求,是其中的一件事情,但如果寿命,这不是一个问题, int
尚未结束。
所以我觉得有renew
假的,程序是很好形成, assert
成功!
随着renew
true,则存储“重复使用”由程序,使原来的寿命int
结束了,而另一个的寿命int
开始。 但随后我们进入3.8第7段:
如果一个对象的生命周期结束之后和其占据的重复使用或释放的对象,则在其原来的对象所占据的存储位置创建一个新对象的存储,即指向的原始对象的指针,参考在此之前称为原始对象,或原始对象的名称将自动引用新的对象,并且一旦新对象的生命周期已经开始,可用于操纵新对象时,如果:
- 新对象存储正好覆盖了原始对象占用的存储位置,
- 新的对象是相同的类型与原始对象(忽略顶层cv修饰符)的,和
- 原始对象的类型不是常量限定,并且,如果一个类型,不包含任何非静态数据成员,其类型为常量限定或引用类型,和
- 原始对象是类型的最派生对象(1.8)
T
和新的对象的类型的一个最派生对象T
(即,它们不是基类的子对象)。
这里的第一点是最棘手的一个。 对于像你的一个标准布局类T
,同一成员一定要始终在同一个存储。 我不能确定这是否在技术上需要时类型不是标准的布局。
虽然无论是ref
仍然可以使用与否,有一个在这个例子中的另一个问题。
12.6.2条第8款:
为类的构造函数调用后X
已经完成,如果成员X
既不是初始化也不构造的主体的化合物语句的执行期间给出的值,所述构件具有不确定的值。
这意味着实现符合,如果它设置t.mem
为零或0xDEADBEEF
(有时调试模式实际上将调用构造函数之前做这样的事情)。
你还没有被破坏的内存,你只有手动称为析构函数(在这种情况下它不是不同的然后调用常用的方法)。 你的内存(栈部分) t
变量没有被“释放”。 所以这个断言将永远与你当前的代码通过。