删除一个NULL指针在写入析构函数时不会调用重载删除

时间:2009-07-10 08:21:29

标签: c++ memory-management destructor

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()时的输出

  

操作员新郎   内存不足

未写入Widget()时的输出

  

操作员新郎   记忆不足
  operator delete

7 个答案:

答案 0 :(得分:21)

我记得在comp.lang.c ++。moderated之前的类似操作符删除。我现在找不到,但答案就是这样说的......

  

不幸的是,语言   规格不够   明确控制是否应该进行   进入重载'运算符删除'   何时调用delete-expression   在对应的空指针上   类型,即使标准确实如此   说删除表达式   空指针是一个无操作。

James Kanze 特别说:

  

它仍然是响应的可靠性   operator delete(或删除[])to   校验;标准不保证   它不会被赋予空指针;   标准要求它是一个   如果给出空指针,则为no-op。或者那个   允许实现调用   它。根据最新的草案,   “第一个论点的价值   提供给释放功能   可以是空指针值;如果是这样,   如果解除分配功能是   一个在标准库中提供,   电话没有效果。“我不是   确定那是什么意思   标准库中提供的一个“   意思是---从字面上看,   因为他的功能不是一个提供的   由标准库,句子   似乎不适用。但不知何故,   这没有意义

我记得这个becoz我曾经有类似的问题,并在.txt文件中保留了答案。

<强> UPDATE-1:

哦,我发现它here。 另请阅读此链接defect report。 所以,答案是 未指定 。章 5.3.5 / 7

答案 1 :(得分:9)

首先,这可以简化为delete (Widget*)0 - 您main()中的其他所有内容都不需要重复此操作。

这是一个代码生成假象,因为1)用户定义的operator delete必须能够处理NULL值,2)编译器尝试生成最佳代码。

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

现在第二种情况 - 析构函数已定义。这意味着您的delete语句实际上扩展为两个调用 - 析构函数和operator delete。但是无法在空指针上安全地调用析构函数,因为它可能会尝试访问类字段(编译器可能会发现您的特定析构函数并没有真正执行它,因此使用null this进行调用是安全的,但是看起来他们在实践中不打扰)。所以它在析构函数调用之前插入一个空检查。一旦检查已经存在,它也可以使用它跳过对operator delete的调用 - 毕竟它必须是无操作的,并且它将在{{{{{{{{{{ {1}}本身,以防指针实际为空。

据我所知,ISO C ++规范无论如何都不能保证这一点。这只是两个编译器在这里进行相同的优化。

答案 2 :(得分:4)

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

当没有析构函数时,编译器只是直接调用delete运算符,而不生成标量删除析构函数。因此,在这种情况下,毕竟会调用delete运算符。

答案 3 :(得分: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 ++上都是如此。

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

    Widget* w = new Widget;

然后事情按预期工作。

答案 4 :(得分:3)

想要留下评论,而不是回答,没有足够的权限成为新成员。

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

你也可以观察到来自构造函数&amp;的消息。析构函数没有显示出来。

但是,在未定义析构函数时会调用delete。 如果直接认为当未定义destrcutor时,C ++ Compiler将其视为任何其他运算符,则默认情况下编译器在未定义时提供析构函数。

答案 5 :(得分:-1)

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

  1. 不会调用需要实例的析构函数
  2. 停止那里的删除操作(速度优化的种类恕我直言)。
  3. 正如尼尔所说,如果w包含一个小部件,它应该可以工作。

答案 6 :(得分:-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; 
}

输出:

  

操作员新郎   小工具()
  〜窗口小部件()
  operator delete