【问题标题】:Does explicitly calling destructor result in Undefined Behavior here?在这里显式调用析构函数会导致未定义的行为吗?
【发布时间】:2011-03-18 12:24:54
【问题描述】:

在我看来,以下代码(来自一些 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?

【问题讨论】:

  • 未定义的行为的全部意义在于它是未定义的。它“有效”的事实只是无限可能之一。
  • 这个析构函数太简单了,没有伤害效果。我相信调用析构函数并不是特例,只是简单的调用类的一个方法。它具有在被释放之前被调用的特殊情况(从删除或范围退出)。
  • @ereOn:谢谢。我知道它在 g++ 中“有效”并不意味着它不是“未定义”。但是,在线答案(可能不正确)不是 UB,这就是我感到困惑的原因。
  • @EXP0:您认为可能不正确的“在线答案”是什么?你能提供一个链接吗?谢谢。
  • 您对析构函数的使用会调用 UB(因为它会导致对析构函数的两次调用),但在某些情况下您确实需要显式调用析构函数(当使用放置 new 时)。 Placement new 是一种边缘情况,除非(或直到)您需要这个不值得担心的高级功能。

标签: c++ destructor undefined-behavior


【解决方案1】:

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

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

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

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

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

T 类型对象的生命周期结束于:

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

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

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

什么是微不足道的析构函数? §12.4/3 说:

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

——其类的所有直接基类都有普通的析构函数和

——对于其类的所有属于类类型(或其数组)的非静态数据成员,每个这样的类都有一个普通的析构函数。

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

【讨论】:

  • 谢谢。我没有标准文件,并意识到我必须付费才能获得一份。您介意提供“非平凡析构函数”的定义吗?问题中的那个是一个重要的析构函数吗?谢谢。
  • @EXP0:我为你添加了一个微不足道的析构函数的定义。问题中的析构函数不是微不足道的析构函数。
  • @JamesMcNellis,该段落所说的与您的解释不同。该段落说调用已删除对象的析构函数,问题是要求在销毁之前调用析构函数作为普通方法。那不是一回事。显式调用析构函数不会删除对象。要显式删除对象,您必须在实例上调用 delete 运算符(并且它不是自动的)顺便说一句,我从未听说过显式调用析构函数的可能性,因为它应该被解释为 ~ 运算符。
【解决方案2】:

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

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

【讨论】:

  • -1 但它不是未定义的。该语言允许以这种方式进行显式调用,因为它可以使用并且很有用。
  • @Elemental:显式的 dtor 调用本身并不是 UB——但最肯定的是两次销毁同一个对象 UB。事实上,这是标准中给出的示例(第 12.4/14 节):“一旦为对象调用析构函数,该对象就不再存在;如果为生命周期已结束的对象调用析构函数,则行为未定义(3.8). [示例:如果自动对象的析构函数被显式调用,并且块随后以通常会调用对象的隐式销毁的方式离开,则行为未定义。]"
【解决方案3】:

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

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

在您的情况下,它不会崩溃,因为析构函数不操纵任何字段;实际上,您的班级根本没有任何数据成员。如果确实如此,并且您在析构函数的主体中以任何方式对其进行了操作,那么您在第二次调用析构函数时可能会遇到运行时异常。

【讨论】:

    【解决方案4】:

    这里的问题是删除/释放和析构函数是独立的构造。很像 new / allocation 和构造函数。可以只做上述其中之一而没有其他。

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

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

    【讨论】:

    • std::vector(以及其他)的代码将显示出对显式 dtor 调用的良好用途。
    • @Jerry,非常正确,但对于 似乎 成为 OP 问题焦点的堆栈值而言并非如此。
    • 是的,有了这个限制,想出一个合理的方案就很难了。
    【解决方案5】:

    它很可能工作正常,因为析构函数没有引用任何类成员变量。如果你尝试在析构函数中delete 一个变量,你可能会在第二次自动调用它时遇到麻烦。

    再一次,对于未定义的行为,谁知道呢? :)

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          【讨论】:

            【解决方案9】:

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

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-03-27
              • 2013-10-02
              • 2014-08-11
              • 2016-09-04
              • 1970-01-01
              • 1970-01-01
              • 2020-09-25
              相关资源
              最近更新 更多