удаление указателя NULL не вызывает перегруженного удаления при записи деструктора - PullRequest
16 голосов
/ 10 июля 2009
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 () не записан

оператор новый
Недостаточно памяти
оператор удаления

Ответы [ 7 ]

21 голосов
/ 10 июля 2009

Я помню что-то похожее на оператор delete некоторое время назад в comp.lang.c ++. Moderated. Я не могу найти его сейчас, но в ответе говорилось что-то вроде этого ...

К сожалению, язык спецификация недостаточно ясно, должен ли контроль идти в перегруженный «оператор удаления» когда вызывается delete-выражение на нулевой указатель соответствующего тип, хотя стандарт делает сказать, что delete-выражение на Нулевой указатель является недопустимым.

И Джеймс Канзе специально сказал:

Это все еще ответственность оператор удалить (или удалить []), чтобы проверять; стандарт не гарантирует что ему не будет дан нулевой указатель; стандарт требует, чтобы это было нет, если дан нулевой указатель. Или это реализация разрешается вызывать Это. Согласно последнему проекту, «Значение первого аргумента поставляется в функцию освобождения может быть нулевым значением указателя; если так, и если функция освобождения один поставляется в стандартной библиотеке, вызов не имеет никакого эффекта. "Я не совсем уверен, что последствия этого " один поставляется в стандартной библиотеке " предназначены для --- буквально, так как его функция не предусмотрена по стандартной библиотеке, предложение кажется, не подойдет. Но почему-то, это не имеет смысла

Я помню этот becoz, у меня когда-то была похожая проблема, и я сохранил ответ в файле .txt.

UPDATE-1:

О, я нашел это здесь . Также прочитайте эту ссылку отчет о дефектах . Итак, ответ: Не указано . Глава 5.3.5 / 7 .

9 голосов
/ 10 июля 2009

Прежде всего, это действительно можно упростить до delete (Widget*)0 - все остальное в вашем main() не нужно для повторения этого.

Это артефакт генерации кода, который происходит потому, что 1) пользовательский operator delete должен иметь возможность обрабатывать значения NULL и 2) компилятор пытается сгенерировать наиболее оптимальный код из возможных.

Сначала давайте рассмотрим случай, когда пользовательский деструктор не задействован. Если это так, то на экземпляре нет кода для запуска, кроме operator delete. Нет смысла проверять нулевое значение перед передачей управления на operator delete, потому что последний все равно должен выполнить проверку; и поэтому компилятор просто генерирует безусловный вызов operator delete (и вы видите, что последний печатает сообщение).

Теперь второй случай - деструктор был определен. Это означает, что ваш оператор delete фактически расширяется до двух вызовов - деструктора и operator delete. Но деструктор нельзя безопасно вызвать по нулевому указателю, потому что он может попытаться получить доступ к полям класса (компилятор может выяснить, что ваш конкретный деструктор на самом деле этого не делает, и поэтому безопасно вызывать с нулевым this, но выглядит как они не беспокоятся на практике). Таким образом, он вставляет нулевую проверку перед вызовом деструктора. И как только проверка уже есть, она может также использовать ее, чтобы пропустить вызов на operator delete - ведь в любом случае она должна быть неактивной, и она избавит от лишней бессмысленной проверки на нуль внутри operator delete сам в случае, если указатель фактически равен нулю.

Насколько я вижу, в этом ничего не гарантируется спецификацией ISO C ++. Просто оба компилятора проводят здесь одну и ту же оптимизацию.

4 голосов
/ 10 июля 2009

Причина в том, что если у вас есть деструктор, вызов оператора удаления выполняется из скалярного деструктора удаления, который в VC содержит вызов как вашего деструктора, так и оператора удаления. Компилятор предоставляет код, который проверяет, пытаетесь ли вы удалить нулевой указатель. Конечно, удаление такого указателя допустимо, но деструктор такого объекта не должен вызываться, поскольку он может содержать использование переменных-членов. Для этого избегается вызов скалярного деструктора удаления, и в результате также исключается вызов оператора удаления.

Когда деструктора нет, компилятор просто вызывает оператор удаления напрямую, без генерации скалярного деструктора удаления. Следовательно, в таких случаях оператор удаления вызывается в конце концов.

3 голосов
/ 10 июля 2009

Хотелось бы оставить комментарий, вместо ответа, не хватало привилегий для нового участника.

Исключение возникает при создании объекта. Деструктор не вызывается, так как сам объект не создается.

Это также можно наблюдать, поскольку сообщения от конструктора и деструктора не отображаются.

Но удаление вызывается, когда деструктор не определен. Если в директиве предполагалось, что когда destrcutor не определен, компилятор C ++ рассматривает его как любой другой оператор, то компилятор по умолчанию предоставляет деструктор, если он не определен.

3 голосов
/ 10 июля 2009

У меня нет хорошего ответа, но я немного упростил проблему. Следующий код удаляет оператор 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 не работает, поэтому компилятору не нужно вызывать оператор delete Если кто-то фактически выделяет объект:

    Widget* w = new Widget;

тогда все работает как положено.

0 голосов
/ 10 июля 2009

Деструктор объекта вызывается перед оператором удаления. Поэтому я думаю, что он пытается вызвать деструктор, понимает, что указатель равен NULL, поэтому

  1. не вызывает деструктор, которому нужен экземпляр
  2. останавливает там операцию удаления (вид оптимизации скорости ИМХО).

Как сказал Нейл, если w содержит виджет, он должен работать.

0 голосов
/ 10 июля 2009

Вы пытались удалить нулевой указатель. Итак, деструктор не вызывали.

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; 
}

Выход:

оператор новый
Widget ()
~ Widget ()
оператор удаления

...