16
class Widget
{
    public:
        Widget() {
            cout<<"~Widget()"<<endl;
        }
        ~Widget() {
            cout<<"~Widget()"<<endl;
        }

    void* operator new(size_t sz) throw(bad_alloc) {
        cout<<"operator new"<<endl;
        throw bad_alloc();
    }

    void operator delete(void *v) {
        cout<<"operator delete"<<endl;
    }

};

int main() 
{
    Widget* w = 0;
    try {
        w = new Widget();
    }
    catch(bad_alloc) {
        cout<<"Out of Memory"<<endl;
    }

    delete w;
    getch();
    return 1;
}

在此代码中,当析构函数存在时,delete w不会调用重载运算符。delete如果省略析构函数,delete则调用重载的。为什么会这样?

写入 ~Widget() 时的输出

运算符 new
内存不足

没有写入~Widget() 时的输出

运算符 new
内存不足
运算符 delete

4

7 回答 7

21

我记得不久前在 comp.lang.c++.moderated 中关于 operator delete 的类似内容。我现在找不到它,但答案是这样的..

不幸的是,语言规范对于在相应类型的空指针上调用 delete-expression 时是否应该进入重载的“operator delete”并不够清楚,即使标准确实说 delete-expression on null -pointer 是无操作的。

詹姆斯坎泽特别说:

检查仍然是操作员删除(或删除[])的责任;该标准不保证不会给它一个空指针;如果给定一个空指针,该标准要求它是一个空操作。或者允许实现调用它。根据最新草案,“提供给释放函数的第一个参数的值可能是空指针值;如果是这样,并且释放函数是标准库中提供的函数,则调用无效。” 我不太确定“是标准库中提供的”的含义是什么——从字面上看,因为他的功能不是标准库提供的,所以这句话似乎不适用. 但不知何故,这没有意义

我记得这是因为我曾经有过类似的问题,并将答案保存在 .txt 文件中。

更新-1:

哦,我在这里找到了。另请阅读此链接缺陷报告。所以,答案是Unspecified。第5.3.5/7章。

于 2009-07-10T09:04:40.480 回答
9

首先,这真的可以简化为delete (Widget*)0- 你的其他一切都不main()需要重现这个。

这是一个代码生成人工制品,因为 1) 用户定义operator delete必须能够处理 NULL 值,以及 2) 编译器试图生成可能的最佳代码。

首先让我们考虑不涉及用户定义的析构函数的情况。如果是这种情况,则没有可在实例上运行的代码,除了operator delete. 在将控制权转移到 之前检查 null 是没有意义的operator delete,因为后者无论如何都应该进行检查;所以编译器只是生成无条件调用operator delete(你会看到后者打印一条消息)。

现在第二种情况——析构函数被定义了。这意味着您的delete语句实际上扩展为两个调用 - 析构函数和operator delete. 但是不能在空指针上安全地调用析构函数,因为它可能会尝试访问类字段(编译器可能会发现您的特定析构函数并没有真正做到这一点,因此使用 null 调用是安全的this,但看起来他们没有在实践中不打扰)。所以它在析构函数调用之前在那里插入一个空检查。并且一旦检查已经存在,它也可以使用它跳过对 的调用operator delete- 毕竟它无论如何都需要成为无操作,并且它会在operator delete自身内部对 null 进行额外的无意义检查,以防指针实际一片空白。

就我所见,ISO C++ 规范对此没有任何保证。只是两个编译器在这里都做了同样的优化。

于 2009-07-10T09:37:08.247 回答
4

原因是如果你有一个析构函数,对 delete 运算符的调用是在标量删除析构函数中完成的,在 VC 中它包含对你的析构函数和 delete 运算符的调用。编译器提供了检查您是否尝试删除 NULL 指针的代码。删除这样的指针当然是合法的,但是不能调用这样的对象的析构函数,因为它可能包含成员变量的使用。为此,避免了对标量删除析构函数的调用,因此也避免了对删除运算符的调用。

没有析构函数时,编译器直接调用删除操作符,不生成标量删除析构函数。因此,在这种情况下,删除操作符毕竟会被调用。

于 2009-07-10T09:06:42.363 回答
3

我没有一个好的答案,但我稍微简化了这个问题。以下代码删除了运算符 new 和异常处理:

#include <iostream>
using namespace std;

class Widget {

  public:
    Widget() {
        cout<<"Widget()"<<endl;
    }
    ~Widget() {
        cout<<"~Widget()"<<endl;
    }

  void operator delete(void *v) {
       cout << "operator delete" << endl;
  }
};

int main() {
    Widget* w = 0;
    cout << "calling delete" << endl;
    delete w;
}

这在 VC++ 和 g++ 上仍然表现出相同的行为和 des。

当然,删除 NULL 指针是空操作,因此编译器不必调用 operator delete。如果实际分配了一个对象:

    Widget* w = new Widget;

然后事情按预期工作。

于 2009-07-10T08:50:13.973 回答
3

想发表评论,而不是回答,作为新成员没有足够的特权。

在创建对象期间引发异常。没有调用析构函数,因为没有创建对象本身。

您还可以观察到,因为没有显示来自构造函数和析构函数的消息。

但是,当未定义析构函数时,将调用删除。如果直接认为当没有定义析构函数时,C++ 编译器将其视为任何其他运算符,编译器默认在未定义时提供析构函数。

于 2009-07-10T09:44:53.130 回答
-1

对象析构函数在删除操作符之前调用。所以我的猜测是它试图调用析构函数,因此意识到指针是 NULL

  1. 不调用需要实例的析构函数
  2. 在那里停止删除操作(恕我直言,一种速度优化)。

正如尼尔所说,如果 w 包含一个小部件,它应该可以工作。

于 2009-07-10T09:07:54.127 回答
-2

您试图删除 NULL 指针。所以,析构函数没有被调用。

class Widget
{   
public:        
    Widget()
    {            
        cout<<"Widget()"<<endl;        
    }       

    ~Widget() 
    {          
        cout<<"~Widget()"<<endl;    
    }    

    void* operator new(size_t sz) throw(bad_alloc) 
    {      
        cout<<"operator new"<<endl;  
        return malloc(sizeof(Widget));
        //throw bad_alloc();    
    }  

    void operator delete(void *v)
    {               
        cout<<"operator delete"<<endl;   
    }
};

int main()
{

    Widget* w = NULL; 
    try 
    {   
        w = new Widget();
        //throw bad_alloc();
    }   
    catch(bad_alloc) 
    {        
        cout<<"Out of Memory"<<endl;  
    }   
    delete w; 
}

输出:

运算符 new
Widget()
~Widget()
运算符删除

于 2009-07-10T09:01:09.317 回答