在我看来,下面的代码(来自一些C ++问题)应该导致UB,但它似乎不是。这是代码:
#include <iostream>
using namespace std;
class some{ public: ~some() { cout<<"some's destructor"<<endl; } };
int main() { some s; s.~some(); }
答案是:
some's destructor
some's destructor
我学习了c ++ faq lite,我们不应该显式调用析构函数。我认为在显式调用析构函数后,应该删除对象s。程序完成后会自动再次调用析构函数,它应该是UB。但是,我在g ++上尝试了它,并获得与上述答案相同的结果。
是因为这个类太简单了(不涉及新的/删除)?或者在这种情况下根本不是UB?
答案 0 :(得分:15)
行为未定义,因为对同一对象调用析构函数两次:
在生命周期结束的对象上调用析构函数会导致每个C ++的未定义行为03§12.4/ 6:
如果为生命周期结束的对象调用析构函数
,则行为未定义
对象的生命周期在根据§3.8/ 1调用析构函数时结束:
类型
T
的对象的生命周期在以下时间结束:- 如果
T
是具有非平凡析构函数(12.4)的类类型,则析构函数调用开始,或- 重用或释放对象占用的存储空间。
请注意,这意味着如果您的类有一个简单的析构函数,行为是明确定义的,因为这种类型的对象的生命周期不会结束,直到它的存储被释放,自动变量直到最后才会发生功能。当然,我不知道为什么你会明确地调用析构函数,如果它是微不足道的。
什么是琐碎的析构函数? §12.4/ 3说:
如果析构函数是隐式声明的析构函数并且如果:
,则析构函数是微不足道的- 其类的所有直接基类都有简单的析构函数和
- 对于类的所有类型(或其数组)的非静态数据成员,每个这样的类都有一个简单的析构函数。
正如其他人所提到的,未定义行为的一个可能结果是您的程序似乎继续正常运行;另一个可能的结果是你的程序崩溃了。任何事情都可能发生,并且没有任何保证。
答案 1 :(得分:6)
这是未定义的行为 - 但与任何UB一样,一种可能性是它(或多或少)似乎起作用,至少对某些工作定义而言。
基本上,您需要(或想要)显式调用析构函数的唯一时间是与placement new结合使用(即,您使用placement new在指定位置创建对象,并使用显式dtor调用来销毁该对象)
答案 2 :(得分:3)
来自http://www.devx.com/tips/Tip/12684
未定义的行为表明当程序达到某个状态时,实现可能会出现不可预测的行为,这几乎毫无例外地是一个错误的结果。未定义的行为可以表现为运行时崩溃,不稳定和不可靠的程序状态,或者 - 在极少数情况下 - 甚至可能未被注意。
在你的情况下,它不会崩溃,因为析构函数不会操纵任何字段;实际上,您的班级根本没有任何数据成员。如果确实如此,并且在析构函数的主体中以任何方式操纵它,那么在第二次调用析构函数时可能会遇到运行时异常。
答案 3 :(得分:1)
这里的问题是删除/释放和析构函数是独立且独立的结构。很像新的/分配和构造函数。没有另一个,可以只做上述之一。
在一般情况下,这种情况确实缺乏实用性,只会导致与堆栈分配值混淆。在我的头脑中,我想不出你想要做到这一点的好方案(虽然我确信有可能存在)。然而,有可能想到这是合法的人为场景。
class StackPointer<T> {
T* m_pData;
public:
StackPointer(T* pData) :m_pData(pData) {}
~StackPointer() {
delete m_pData;
m_pData = NULL;
}
StackPointer& operator=(T* pOther) {
this->~StackPointer();
m_pData = pOther;
return this;
}
};
注意:请不要以这种方式编写类。请改为使用显式Release方法。
答案 4 :(得分:1)
它很可能正常工作,因为析构函数不引用任何类成员变量。如果你试图在析构函数中delete
一个变量,那么第二次自动调用它时可能会遇到麻烦。
然后,对于未定义的行为,谁知道? :)
答案 5 :(得分:0)
主要功能的作用是在堆栈上保留空间,调用某些构造函数,最后调用一些析构函数。这总是发生在局部变量中,无论你在函数中放入什么代码。 您的编译器不会检测到您手动调用析构函数。
无论如何,你不应该手动调用对象的析构函数,除了使用placement-new创建的对象。
答案 6 :(得分:0)
我相信如果您希望您的代码正常,您只需要调用placement new并在退出之前重新填充。对析构函数的调用不是问题,它是你离开作用域时对析构函数的第二次调用。
答案 7 :(得分:0)
您能定义您期望的未定义行为吗?未定义并不意味着随机(或灾难性):给定程序的行为在调用之间可能是可重复的,它只是意味着您无法依赖于任何特定行为,因为它未定义且无法保证将会发生什么。 / p>
答案 8 :(得分:0)
这是未定义的行为。未定义的行为是双析构函数调用,而不是析构函数调用本身。如果您将示例修改为:
#include <iostream>
using namespace std;
class some{ public: ~some() { [INSERT ANY CODE HERE] } };
int main() { some s; s.~some(); }
其中 [INSERT ANY CODE HERE] 可以替换为任意代码。结果具有不可预测的副作用,这就是为什么它被认为是未定义的。