为什么在运算符delete中不调用析构函数?


16

我试图在其中召唤::delete一堂课operator delete。但是不调用析构函数。

我定义的类MyClass,其operator delete过载。全局operator delete也超载。重载operator deleteMyClass将调用重载的全局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...

实际:

只有一个调用重载之前调用析构函数operator deleteMyClass

预期:

有两个对析构函数的调用。调用重载之前的operator delete一个MyClass。另一个才叫全球operator delete


6
MyClass::operator new()应该分配(至少)size字节的原始内存。它不应尝试完全构建的实例MyClass。的构造函数在MyClass之后执行MyClass::operator new()。然后,deletein中的表达式main()调用析构函数,并释放内存(无需再次调用析构函数)。该::delete p表达式没有关于对象p指向的类型的信息,因为它p是a void *,因此无法调用析构函数。
彼得


2
已经提供给您的答案是正确的,但是我想知道:您为什么要尝试覆盖new和delete?典型的用例是实现自定义内存管理(GC,不是来自默认malloc()的内存等)。也许您使用了错误的工具来实现目标。
noamtm

2
::delete p;导致不确定的行为,因为的类型与*p要删除的对象的类型不同(也没有带有虚拟析构函数的基类)
MM

@MM主要编译器最多只会警告它,所以我没有意识到void*操作数甚至是显式的错误格式。[expr.delete] / 1:“ 操作数应是指向对象类型或类类型的指针。[...]这意味着不能使用类型为void的指针删除对象,因为void不是对象类型。 *“ @OP我已经修改了答案。
胡桃木

Answers:


17

您正在滥用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

deletedelete myClass;负责调用析构函数。

::delete p;不调用析构函数,因为它p具有类型void*,因此表达式无法知道要调用的析构函数。::operator delete尽管将a void*操作数用作delete-expression的格式不正确,但它可能会调用您的replaced 来分配内存。

::new MyClass();调用您替换::operator new的对象来分配内存并在其中构造一个对象。返回该对象的指针,指向中void*的new-expression MyClass* myClass = new MyClass();,它将在该内存中构造另一个对象,从而在不调用其析构函数的情况下结束前一个对象的生存期。


编辑:

感谢@MM对这个问题的评论,我意识到void*as的操作数::delete实际上是格式错误的。([expr.delete] / 1)但是,主要的编译器似乎决定只警告这一点,而不是错误。它是由前形成不良的,使用::deletevoid*就已经不确定的行为,看到了这个问题

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


正如@SanderDeDycker在其答案下方指出的那样,您还具有未定义的行为,因为通过在内存中构造一个已经包含MyClass对象的对象而不先调用该对象的析构函数,您就违反了[basic.life] / 5,如果程序取决于析构函数的副作用。在这种情况下,printf析构函数中的语句具有这种副作用。


滥用旨在检查这些操作员的工作方式。但是,感谢您的回答。似乎是解决我问题的唯一答案。
expinc

13

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

在特定于类的中operator new,直接调用全局运算符:

return ::operator new(size);

同样,在特定于类的中operator delete,执行以下操作:

::operator delete(p);

operator new有关更多详细信息,请参见参考页。


我知道在构造函数new中调用:: new会两次调用构造函数,这是我的意思。我的问题是,为什么在运算符delete中调用:: delete时不调用析构函数?
expinc

1
@expinc:故意调用构造函数第二次,而不调用析构函数首先是一个非常糟糕的主意。对于非平凡的析构函数(如您的析构函数),您甚至会冒险进入未定义的行为领域(如果您依赖于析构函数的副作用,则可以这样做)-参考。[基本生活]§5。不要这样
桑德·戴克

1

请参阅CPP参考

operator deleteoperator delete[]

取消分配先前由match分配的存储operator new。这些释放函数由delete-expressions和new-expressions调用,以在具有动态存储持续时间的对象被破坏(或无法构造)之后释放内存。也可以使用常规函数调用语法来调用它们。

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

因此,很清楚并且期望析构函数仅被调用一次-清理对象的实例。它将被调用两次,每个析构函数都必须检查它是否已经被调用。


1
删除运算符仍应隐式调用析构函数,正如您在删除实例后从自己的日志中看到的析构函数所示。这里的问题是,他的类删除覆盖调用了:: delete,这导致了他的全局删除覆盖。全局删除覆盖仅释放内存,因此不会重新调用析构函数。
Pickle Rick

参考资料清楚地指出,删除操作称为对象解构之后
Mario The Spoon,

是的,在类删除之后调用全局删除。这里有两个替代。
Pickle Rick

2
@PickleRick-确实,删除表达式应该调用析构函数(假设提供了带有析构函数的类型的指针)或一组析构函数(数组形式),但operator delete()函数与删除表达式并不相同。在调用operator delete()函数之前先调用析构函数。
彼得

1
如果将标题添加到qoute,这将有所帮助。目前尚不清楚它指的是什么。“删除存储...”-谁取消分配存储?
idclev 463035818
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.