【问题标题】:An interesting case of delete and destructor (C++)一个有趣的删除和析构函数案例(C++)
【发布时间】:2011-01-30 14:43:28
【问题描述】:

我有一段代码,我可以在其中多次调用析构函数并访问成员函数,即使在调用析构函数时保留了成员变量的值。在我调用delete 之后,我仍然能够访问成员函数,但是成员变量被无效(全部为 0)。我不能加倍delete。请解释一下。

#include <iostream>
using namespace std;

template <typename T>
void destroy(T* ptr)
{
    ptr->~T();
}

class Testing
{
public:
    Testing() : test(20)
    {

    }

    ~Testing()
    {
        printf("Testing is being killed!\n");
    }

    int getTest() const
    {
        return test;
    }

private:
    int test;
};

int main()
{
    Testing *t = new Testing();
    cout << "t->getTest() = " << t->getTest() << endl;

    destroy(t);
    cout << "t->getTest() = " << t->getTest() << endl;

    t->~Testing();
    cout << "t->getTest() = " << t->getTest() << endl;

    delete t;
    cout << "t->getTest() = " << t->getTest() << endl;

    destroy(t);
    cout << "t->getTest() = " << t->getTest() << endl;

    t->~Testing();
    cout << "t->getTest() = " << t->getTest() << endl;

    //delete t; // <======== Don't do it! Double free/delete!
    cout << "t->getTest() = " << t->getTest() << endl;

    return 0;
}

【问题讨论】:

    标签: c++ memory-management destructor


    【解决方案1】:

    "一旦一个析构函数被调用 对象,对象不再存在; 行为是未定义的,如果 为对象调用析构函数 谁的生命已经结束”

    C++ draft standard §12.4.12

    正如其他人所指出的,这并不意味着实现总是会做一些明显不需要的事情(比如分段错误)。这意味着它可以做任何最方便的事情。

    【讨论】:

      【解决方案2】:

      像在destroy() 中所做的那样,直接在main() 中直接调用析构函数实际上不会导致对象在C++ 中被销毁。只有此代码中的 delete 语句可以做到这一点。由于 T 的析构函数是良性的(它只是打印),这几乎没有任何效果。

      由于没有一个成员函数是虚函数,因此在销毁后调用它们仍然会得到正确的代码来执行。一旦到达那里,this 指针可能无效(在您调用 delete 之后),但这不会阻止代码取消引用指针并返回 int 成员值的值。

      【讨论】:

      【解决方案3】:

      你真的不应该调用一个类的析构函数(除非你使用placement new)但是为了回答你的问题,一旦内存被删除,访问指向该内存的指针会导致未定义的行为。在您的情况下,您正在使用的内存似乎已被释放以供将来使用,但尚未被覆盖。所以你仍然可以访问它,但不能保证该内存何时会被其他东西使用。

      【讨论】:

      • +1 谢谢尼基!是的,你是对的。我正在编写一个带有新位置和删除位置的内存池。不确定我是否可以像destroy(T * ptr)那样在模板中编写放置删除。请指教。
      【解决方案4】:

      你已经调用了未定义的行为,所有的赌注都被取消了。

      【讨论】:

        【解决方案5】:

        仅仅因为你正在访问一个不再有效的对象并不意味着你的程序必须爆炸,它只是意味着你的程序可以爆炸。

        这是未定义的行为,这意味着任何事情都可能发生,它甚至可能看起来做正确的事情。

        【讨论】:

        • 恶魔会从你的鼻子里飞出来,别忘了。
        • @greyfade - 不,恶魔可能从你的鼻子里飞出。无论哪种方式都无法保证。
        【解决方案6】:

        这是未定义行为的表现。通过已删除的指针调用成员函数,一切正常 - 编译器和运行时不需要检查此错误,但您当然不能指望它工作。

        这与使用已释放的内存属于类似的类别 - 您可能会在那里找到旧数据(或者您可能找不到)。您可能会导致程序崩溃(或不崩溃)。您甚至可以毫无怨言地更改数据。

        但无论如何,这是一个编程错误。

        【讨论】:

        • 请问为什么delete可以调用一次而析构函数可以调用多次?
        • @Viet,删除运算符调用析构函数,然后除此之外释放内存。析构函数本身不会释放对象所在的内存(这就是为什么析构函数即使在您使用自动存储分配来创建对象时也能工作的原因)。从技术上讲,你也不应该多次调用析构函数(你不应该直接调用析构函数,应该使用作用域或删除运算符来调用它),但是你的析构函数的实现不会检查多个调用,而删除可能。
        • 按照您的操作显式调用析构函数并不涉及释放内存。它仍然是一个编程错误,只是没有为您发现它。两次释放内存(通过调用删除)可能导致堆管理器崩溃或以其他方式抱怨。请注意,虽然允许显式调用析构函数,但通常不会这样做,因为析构函数将通过delete 或对象超出范围(或其他 RAII 场景)调用。我唯一一次看到显式调用 dtor 的有效实例是在使用放置 new 时。
        • @Viet,你不能——看起来你可以。未定义的行为意味着一切皆有可能,包括看起来有效。
        • 在 SO 上放置 new 已经有了很好的答案。另请参阅:C++ 常见问题解答 (parashift.com/c++-faq-lite/dtors.html#faq-11.10)
        猜你喜欢
        • 2011-01-14
        • 2014-11-01
        • 2012-09-01
        • 2014-03-13
        • 2020-10-18
        • 2013-05-30
        • 1970-01-01
        • 2013-10-04
        • 1970-01-01
        相关资源
        最近更新 更多