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

时间:2019-10-21 09:47:40

标签: c++ delete-operator

我尝试调用::deleteoperator delete中的一个类。但是不会调用析构函数。

我定义了一个类MyClass,其operator delete已被重载。全局operator delete也超载。 operator delete中重载的MyClass将调用全局重载的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 delete的重载MyClass之前,只有一个对析构函数的调用。

预期:

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

3 个答案:

答案 0 :(得分:17)

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

这些函数的全局版本为::operator new::operator delete::new::delete是new / delete-expressions,new / delete与它们不同的是::new::delete绕过特定于类的operator new / operator delete重载。

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

由于您的重载仅负责分配/取消分配部分,因此应调用::operator new::operator delete而不是::new::delete

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

::delete p;不会调用析构函数,因为p的类型为void*,因此表达式无法知道要调用的析构函数。尽管将::operator delete用作 delete-expression 的操作数是不正确的格式,但它可能会调用您替换的void*来释放内存(见下文编辑)。

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


编辑:

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

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


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

答案 1 :(得分:14)

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

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

return ::operator new(size);

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

::operator delete(p);

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

答案 2 :(得分:1)

请参见CPP Reference

  

import java.util.Scanner; public class Main { public static int factorial(myFunc mf, int n) { return mf.func(n); } public static int fact() { Factorial f = new Factorial(); System.out.println("Enter a number: "); Scanner scanner = new Scanner(System.in); int inINT = scanner.nextInt(); int outINT = factorial(f::Factor, inINT); System.out.println(outINT); return outINT; } public static void main(String args[]) { fact(); } } operator delete

     

取消分配先前由匹配的operator delete[]分配的存储。   这些释放函数由delete-expressions和   new-expressions在销毁(或无法执行以下操作)后重新分配内存   构造)具有动态存储期限的对象。他们可能也是   使用常规函数调用语法调用。

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

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