显式调用析构函数是否会导致此处的未定义行为?

发布于 2024-09-10 11:46:44 字数 484 浏览 4 评论 0原文

在我看来,以下代码(来自一些 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++ 上尝试了一下,得到了与上面答案相同的结果。

是因为类太简单(不涉及new/delete)吗?或者在这种情况下根本不是UB?

In my opinion, the following code (from some C++ question) should lead to UB, but the it seems it is not. Here is the code:

#include <iostream>
using namespace std;
class some{ public: ~some() { cout<<"some's destructor"<<endl; } };
int main() { some s; s.~some(); }

and the answer is:

some's destructor
some's destructor

I learned form c++ faq lite that we should not explicitly call destructor. I think after the explicitly call to the destructor, the object s should be deleted. The program automatically calls the destructor again when it's finished, it should be UB. However, I tried it on g++, and get the same result as the above answer.

Is it because the class is too simple (no new/delete involved)? Or it's not UB at all in this case?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

蓝梦月影 2024-09-17 11:46:44

该行为是未定义的,因为对同一对象调用析构函数两次:

  • 一次当您显式调用它时
  • 一次 当作用域结束并且自动变量被销毁时

对生命周期已结束的对象调用析构函数会导致每个 C++ 的未定义行为03§12.4/6:

如果为生命周期已结束的对象调用析构函数,则行为未定义

根据 §3.8/1 调用其析构函数时,对象的生命周期结束:

T 类型的对象的生命周期在以下情况结束:

—如果 T 是具有重要析构函数的类类型 (12.4),则析构函数调用开始,或者

——对象占用的存储被重用或释放。

请注意,这意味着如果您的类有一个简单的析构函数,则该行为是明确定义的,因为这种类型的对象的生命周期在其存储空间被释放之前不会结束,对于自动变量而言,直到函数结束时才会发生这种情况。当然,我不知道为什么你会显式调用析构函数,如果它是微不足道的。

什么是平凡的析构函数? §12.4/3 说:

如果析构函数是隐式声明的析构函数并且满足以下条件,则该析构函数是微不足道的:

——该类的所有直接基类都有简单的析构函数,并且

——对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个简单的析构函数。

正如其他人提到的,未定义行为的一个可能结果是您的程序似乎继续正确运行;另一个可能的结果是你的程序崩溃。任何事情都有可能发生,而且没有任何保证。

The behavior is undefined because the destructor is invoked twice for the same object:

  • Once when you invoke it explicitly
  • Once when the scope ends and the automatic variable is destroyed

Invoking the destructor on an object whose lifetime has ended results in undefined behavior per C++03 §12.4/6:

the behavior is undefined if the destructor is invoked for an object whose lifetime has ended

An object's lifetime ends when its destructor is called per §3.8/1:

The lifetime of an object of type T ends when:

— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or

— the storage which the object occupies is reused or released.

Note that this means if your class has a trivial destructor, the behavior is well-defined because the lifetime of an object of such a type does not end until its storage is released, which for automatic variables does not happen until the end of the function. Of course, I don't know why you would explicitly invoke the destructor if it is trivial.

What is a trivial destructor? §12.4/3 says:

A destructor is trivial if it is an implicitly-declared destructor and if:

— all of the direct base classes of its class have trivial destructors and

— for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

As others have mentioned, one possible result of undefined behavior is your program appearing to continue running correctly; another possible result is your program crashing. Anything can happen and there are no guarantees whatsoever.

音盲 2024-09-17 11:46:44

这是未定义的行为——但与任何 UB 一样,一种可能性是它(或多或少)似乎有效,至少对于工作的某些定义而言。

本质上,您需要(或想要)显式调用析构函数的唯一时间是与放置 new 结合使用(即,使用放置 new 在指定位置创建对象,并使用显式 dtor 调用来销毁该对象)。

It's undefined behavior -- but as with any UB, one possibility is that it (more or less) appears to work, at least for some definition of work.

Essentially the only time you need (or want) to explicitly invoke a destructor is in conjunction with placement new (i.e., you use placement new to create an object at a specified location, and an explicit dtor invocation to destroy that object).

孤凫 2024-09-17 11:46:44

来自 http://www.devx.com/tips/Tip/12684

未定义的行为表示当程序达到特定状态时,实现可能会出现不可预测的行为,这几乎无一例外都是错误的结果。未定义的行为可能表现为运行时崩溃、不稳定且不可靠的程序状态,或者在极少数情况下,它甚至可能未被注意到

在您的情况下,它不会崩溃,因为析构函数不操作任何字段;实际上,您的类根本没有任何数据成员。如果确实如此,并且在析构函数体内以任何方式操纵它,则在第二次调用析构函数时可能会出现运行时异常。

From http://www.devx.com/tips/Tip/12684

Undefined behavior indicates that an implementation may behave unpredictably when a program reaches a certain state, which almost without exception is a result of a bug. Undefined behavior can be manifested as a run time crash, unstable and unreliable program state, or--in rare cases--it may even pass unnoticed.

In your case it doesn't crash because the destructor doesn't manipulate any field; actually, your class doesn't have any data members at all. If it did and in destructor's body you manipulated it in any way, you would likely get a run-time exception while calling destructor for the second time.

双手揣兜 2024-09-17 11:46:44

这里的问题是删除/释放和析构函数是单独且独立的构造。很像 new / 分配和构造函数。可以仅执行上述操作之一而不执行其他操作。

在一般情况下,这种情况确实缺乏用处,只会导致与堆栈分配值的混淆。在我的脑海中,我想不出一个好的场景,你会想要这样做(尽管我确信可能有一个)。然而,可以想象这种行为是合法的人为场景。

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 方法。

The problem here is that deletion / deallocation and destructors are separate and independent constructs. Much like new / allocation and constructors. It is possible to do only one of the above without the other.

In the general case this scenario does lack usefulness and just lead to confusion with stack allocated values. Off the top of my head I can't think of a good scenario where you would want to do this (although I'm sure there is potentially one). However it is possible to think of contrived scenarios where this would be legal.

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;
  }
};

Note: Please don't ever code a class this way. Have an explicit Release method instead.

多彩岁月 2024-09-17 11:46:44

它很可能工作正常,因为析构函数不引用任何类成员变量。如果您尝试在析构函数中删除变量,那么当它第二次被自动调用时,您可能会遇到麻烦。

话又说回来,对于未定义的行为,谁知道呢? :)

It most likely works fine because the destructor does not reference any class member variables. If you tried to delete a variable within the destructor you would probably run into trouble when it is automatically called the second time.

Then again, with undefined behavior, who knows? :)

我为君王 2024-09-17 11:46:44

main 函数的作用是在堆栈上保留空间,调用 some 的构造函数,最后调用 some 的析构函数。无论您在函数中放入什么代码,这种情况总是会发生在局部变量上。
您的编译器不会检测到您手动调用了析构函数。

无论如何,你不应该手动调用对象的析构函数,除了使用placement-new创建的对象。

What the main function does is reserving space on the stack, calling some's constructor, and at the end calling some's destructor. This always happens with a local variable, whatever code you put inside the function.
Your compiler won't detect that you manually called the destructor.

Anyway you should never manually call an object's destructor, except for objects created with placement-new.

简美 2024-09-17 11:46:44

我相信,如果您希望代码正常,您只需调用placement new 并在退出之前将其填回即可。对析构函数的调用不是问题,这是离开作用域时对析构函数的第二次调用。

I believe that if you want your code to be OK you simply need to call placement new and fill it back in before exiting. The call to the destructor isn't the issue, it's the second call to the destructor made when you leave scope.

往事随风而去 2024-09-17 11:46:44

您能定义您期望的未定义行为吗?未定义并不意味着随机(或灾难性):给定程序的行为在调用之间可能是可重复的,它只是意味着您不能依赖任何特定行为,因为它是未定义的并且无法保证会发生什么。

Can you define the undefined behaviour you expect? Undefined doesn't mean random (or catastrophic): the behaviour of a given program may be repeatable between invocations, it just means you can't RELY on any particular behaviour because it is undefined and there is no guarantee of what will happen.

你对谁都笑 2024-09-17 11:46:44

这是未定义的行为。未定义的行为是双重析构函数调用,而不是析构函数调用本身。如果将示例修改为:

#include <iostream>
using namespace std;
class some{ public: ~some() { [INSERT ANY CODE HERE] } };
int main() { some s; s.~some(); }

其中 [在此插入任何代码] 可以替换为任意代码。结果具有不可预测的副作用,这就是它被认为是未定义的原因。

It is undefined behaviour. The undefined behaviour is the double destructor call and not with the destructor call itself. If you modify your example to:

#include <iostream>
using namespace std;
class some{ public: ~some() { [INSERT ANY CODE HERE] } };
int main() { some s; s.~some(); }

where [INSERT ANY CODE HERE] can be replaced with any arbitrary code. The results have unpredictable side effects, which is why it is considered undefined.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文