【问题标题】:Why is the destructor not called in operator delete?为什么在 operator delete 中没有调用析构函数?
【发布时间】:2020-02-17 09:23:15
【问题描述】:

我尝试调用::delete 来获取operator delete 中的一个类。但是没有调用析构函数。

我定义了一个类MyClass,其operator delete 已被重载。全局 operator delete 也被重载。 MyClass 的重载operator delete 将调用重载的全局operator delete

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

输出是:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

实际:

在调用MyClass 的重载operator delete 之前,只调用了一次析构函数。

预期:

对析构函数有两个调用。调用MyClass 的重载operator delete 之前的一个。另一个之前打电话给全球operator delete

【问题讨论】:

  • MyClass::operator new() 应该分配(至少)size 字节的原始内存。它不应该尝试完全构造MyClass 的实例。 MyClass 的构造函数在MyClass::operator new() 之后执行。然后,main() 中的delete 表达式调用析构函数,并释放内存(不再调用析构函数)。 ::delete p 表达式没有关于 p 指向的对象类型的信息,因为 pvoid *,因此无法调用析构函数。
  • 已经给你的回答是正确的,但我想知道:为什么你要覆盖 new 和 delete?典型的用例是实现自定义内存管理(GC、不是来自默认 malloc() 的内存等)。也许您使用了错误的工具来实现您想要实现的目标。
  • ::delete p; 导致未定义的行为,因为 *p 的类型与被删除对象的类型不同(也不是具有虚拟析构函数的基类)
  • @M.M 主要编译器最多只会警告它,所以我没有意识到 void* 作为操作数甚至是明确的格式错误。 [expr.delete]/1: "操作数应该是指向对象类型或类类型的指针。[...] 这意味着不能使用 void 类型的指针删除对象,因为 void 不是对象类型。*”@OP 我已经修改了我的答案。

标签: c++ delete-operator


【解决方案1】:

您误用了operator newoperator delete。这些运算符是分配和释放函数。他们不负责构造或破坏对象。他们只负责提供放置对象的内存。

这些函数的全局版本是::operator new::operator delete::new::delete 是新/删除表达式,new/delete 也是如此,与这些不同的是,::new::delete 将绕过特定于类的 operator new/operator delete 重载.

new/delete-expressions 构造/销毁分配/解除分配(通过在构造之前或销毁之后调用适当的operator newoperator delete)。

由于您的重载只负责分配/解除分配部分,它应该调用::operator new::operator delete 而不是::new::delete

delete myClass; 中的delete 负责调用析构函数。

::delete p; 不调用析构函数,因为p 的类型为void*,因此表达式不知道要调用什么析构函数。它可能会调用您替换的::operator delete 来释放内存,尽管使用void* 作为delete-expression 的操作数是不正确的(请参阅下面的编辑)。

::new MyClass(); 调用你替换的::operator new 来分配内存并在其中构造一个对象。指向该对象的指针作为void* 返回到MyClass* myClass = new MyClass(); 中的新表达式,然后它将在此内存中构造另一个对象,结束前一个对象的生命周期而不调用其析构函数。


编辑:

感谢@M.M 对该问题的评论,我意识到void* 作为::delete 的操作数实际上是不正确的。 ([expr.delete]/1) 然而,主要的编译器似乎决定只警告这一点,而不是错误。在其格式错误之前,在 void* 上使用 ::delete 已经具有未定义的行为,请参阅 this question

因此,您的程序格式错误,如果代码仍然能够编译,您无法保证代码确实执行了我上面描述的操作。


正如@SanderDeDycker 在他的回答下面指出的那样,您也有未定义的行为,因为通过在内存中构造另一个已经包含MyClass 对象的对象而不首先调用该对象的析构函数,您违反了[basic.life]/5,它禁止这样做如果程序依赖于析构函数的副作用。在这种情况下,析构函数中的printf 语句就有这样的副作用。

【讨论】:

  • 误用是为了检查这些操作符是如何工作的。不过,谢谢你的回答。这似乎是解决我问题的唯一答案。
【解决方案2】:

您的特定于类的重载未正确完成。这可以在您的输出中看到:构造函数被调用了两次!

在类特定的operator new中,直接调用全局操作符:

return ::operator new(size);

类似地,在特定于类的operator delete 中,执行:

::operator delete(p);

有关详细信息,请参阅operator new 参考页面。

【讨论】:

  • 我知道在 operator new 中调用 ::new 会调用构造函数两次,我是认真的。我的问题是为什么在 operator delete 中调用 ::delete 时不调用析构函数?
  • @expinc :故意第二次调用构造函数而不调用析构函数 first 是一个非常糟糕的主意。对于非平凡的析构函数(如你的),你甚至冒险进入未定义的行为领域(如果你依赖析构函数的副作用,你会这样做) - 参考。 [basic.life] §5。不要这样做。
【解决方案3】:

CPP Reference:

operator delete, operator delete[]

释放先前由匹配的operator new 分配的存储空间。 这些释放函数由 delete-expressions 和 销毁后释放内存的新表达式(或未能 构造)具有动态存储持续时间的对象。他们也可能是 使用常规函数调用语法调用。

删除(和新建)只负责“内存管理”部分。

所以很明显,析构函数只被调用一次——清理对象的实例。它会被调用两次吗,每个析构函数都必须检查它是否已经被调用过。

【讨论】:

  • delete 操作符仍应隐式调用析构函数,正如您从他自己的日志中看到的那样,在删除实例后显示析构函数。这里的问题是他的类删除覆盖调用 ::delete 导致他的全局删除覆盖。该全局删除覆盖仅释放内存,因此不会重新调用析构函数。
  • 参考文献明确指出,delete 被称为 AFTER object deconstruction
  • 是的,在类delete之后调用全局delete。这里有两个覆盖。
  • @PickleRick - 虽然删除 expression 确实应该调用析构函数(假设提供了指向带有析构函数的类型的指针)或一组析构函数(数组形式),operator delete() 函数与删除表达式不同。在调用operator delete() 函数之前调用析构函数。
  • 如果你在 qoute 中添加一个标题会有所帮助。目前尚不清楚它指的是什么。 "Dellocates storage..." - 谁释放存储空间?
猜你喜欢
  • 2017-04-09
  • 1970-01-01
  • 2018-11-19
  • 2017-03-22
  • 1970-01-01
  • 2021-08-04
  • 2017-01-10
  • 1970-01-01
  • 2011-04-18
相关资源
最近更新 更多