Meh*_*dad 24 c++ destructor undefined-behavior language-lawyer
注意:我见过类似的问题,但没有一个答案是准确的,所以我自己也会这样问.
C++标准说:
程序可以通过重用对象占用的存储来结束任何对象的生命周期,或者通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期.对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数; 但是,如果没有对析构函数的显式调用或者如果没有使用delete-expression来释放存储,则不应该隐式调用析构函数,并且任何依赖于析构函数产生的副作用的程序都有未定义的行为.
我根本不明白"取决于副作用"是什么意思.
一般问题是:
举例说明我的观点是:
考虑下面这样的程序.还要考虑明显的变化(例如,如果我不在另一个上构造一个对象,但我仍然忘记调用析构函数,如果我不打印输出来观察它,等等):
#include <math.h>
#include <stdio.h>
struct MakeRandom
{
int *p;
MakeRandom(int *p) : p(p) { *p = rand(); }
~MakeRandom() { *p ^= rand(); }
};
int main()
{
srand((unsigned) time(NULL)); // Set a random seed... not so important
// In C++11 we could use std::random_xyz instead, that's not the point
int x = 0;
MakeRandom *r = new MakeRandom(&x); // Oops, forgot to call the destructor
new (r) MakeRandom(&x); // Heck, I'll make another object on top
r->~MakeRandom(); // I'll remember to destroy this one!
printf("%d", x); // ... so is this undefined behavior!?!
// If it's indeed UB: now what if I didn't print anything?
}
Run Code Online (Sandbox Code Playgroud)
我说这显示出"未定义的行为"似乎是荒谬的,因为x它已经是随机的 - 因此,对它进行异或,另一个随机数不能真正使程序比以前更"未定义",可以吗?
此外,在什么时候说程序"取决于"析构函数是正确的?如果值是随机的,它是否会这样做 - 或者一般情况下,如果我无法区分析构函数与运行与未运行?如果我从未读过这个价值怎么办?基本上:
究竟是哪个表达式或语句引起了这个,为什么?
我根本不明白"取决于副作用"是什么意思.
这意味着它取决于析构函数正在做的事情.在您的示例中,修改*p或不修改它.您的代码中存在依赖关系,因为如果不调用dctor,输出会有所不同.
在当前代码中,打印的数字可能与第二次rand()调用返回的数字不同.你的程序会调用未定义的行为,但这只是UB在这里没有任何不良影响.
如果您不打印该值(或以其他方式读取它),则不会对dcor的副作用产生任何依赖性,因此也不会有UB.
所以:
是忘记调用析构函数而不是忘记使用同一个函数调用普通函数?
不,在这方面没有任何不同.如果你依赖它被调用,你必须确保它被调用,否则你的依赖不满足.
此外,在什么时候说程序"取决于"析构函数是正确的?如果值是随机的,它是否会这样做 - 或者一般情况下,如果我无法区分析构函数与运行与未运行?
随机与否并不重要,因为代码取决于要写入的变量.仅仅因为很难预测新值是什么并不意味着没有依赖性.
如果我从未读过这个价值怎么办?
然后没有UB,因为代码在写入之后对变量没有依赖性.
在哪个条件(如果有)下,此程序是否显示未定义的行为?
没有条件.它始终是UB.
究竟是哪个表达式或语句引起了这个,为什么?
表达方式:
printf("%d", x);
Run Code Online (Sandbox Code Playgroud)
因为它引入了对受影响变量的依赖性.
如果您接受标准要求在析构函数影响程序行为的情况下通过销毁来平衡分配,则这是有意义的.即唯一可信的解释是,如果一个程序
delete)的物体上,并然后该计划注定了UB的土地.(OTOH,如果析构函数不影响程序行为,那么你就可以了.你可以跳过这个电话.)
注意添加副作用在本SO文章中讨论,我在此不再重复.一个保守的推论是"程序......依赖于析构函数"相当于"析构函数具有副作用".
附加说明然而,该标准似乎允许更自由的解释.它没有正式定义程序的依赖性.(它确实将表达的特定质量定义为依赖性,但这不适用于此.)然而,在"A依赖于B"和"A依赖于B"的衍生物的100多种用法中,它采用了传统的这个词的意义:B的变化直接导致A的变化.因此,推断程序P依赖于副作用E似乎没有一个飞跃,以至于E的表现或不表现导致变异在执行P期间的可观察行为中.在这里,我们坚实的基础.程序的含义 - 它的语义 - 在标准下等同于其在执行期间的可观察行为,并且这是明确定义的.
符合实施的最低要求是:
严格根据抽象机器的规则来评估对volatile对象的访问.
在程序终止时,写入文件的所有数据应与根据抽象语义产生的程序执行的可能结果之一相同.
交互设备的输入和输出动态应以这样一种方式进行,即在程序等待输入之前提示输出实际被传送.构成交互设备的是实现定义的.
这些统称为程序的可观察行为.
因此,根据标准的约定,如果析构函数的副作用最终会影响易失性存储访问,输入或输出,并且从不调用析构函数,则程序具有UB.
换句话说:如果你的析构函数做了重要的事情并且没有被一直调用,你的程序(标准说)应该被考虑,并且在此声明,没用.
对于语言标准,这是否过于严格,可能是迂腐?(毕竟,标准防止发生副作用,由于隐式析构函数调用,然后drubs你,如果析构函数会导致可观察行为的变化,如果它已经被称为!)也许是这样.但它作为一种坚持良好形成的计划的方式确实有意义.
Sea*_*ean -1
假设您有一个类在其构造函数中获取锁,然后在其析构函数中释放锁。释放锁是调用析构函数的副作用。
现在,您的工作是确保调用析构函数。通常,这是通过调用 来完成的delete,但您也可以直接调用它,如果您使用placement new 分配了一个对象,通常会这样做。
在您的示例中,您分配了 2 个MakeRandom实例,但只调用了其中一个实例的析构函数。如果它正在管理某些资源(例如文件),那么您就会遇到资源泄漏。
所以,回答你的问题,是的,忘记调用析构函数与忘记调用普通函数不同。析构函数是构造函数的逆函数。您需要调用构造函数,因此您需要调用析构函数才能“展开”析构函数所做的任何操作。“普通”函数的情况并非如此。