Подтвердить что ты не робот

Удаление указателя 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;
        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() не записан

новый оператор Из памяти
оператор delete

4b9b3361

Ответ 1

Я помню что-то подобное в операторе delete a while ago в comp.lang.С++. moderated. Я не могу найти его сейчас, но ответ сказал что-то вроде этого.

К сожалению, язык спецификации недостаточно ясно, следует ли контролировать в перегруженный 'оператор delete' при вызове delete-expression по нулевому указателю соответствующих типа, хотя стандарт скажем, что удаление null-pointer - это no-op.

И Джеймс Канзе конкретно сказал:

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

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

ОБНОВЛЕНИЕ-1:

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

Ответ 2

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

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

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

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

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

Ответ 3

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

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

Ответ 4

У меня нет хорошего ответа, но я немного упростил проблему. Следующий код удаляет оператор 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;
}

Это по-прежнему демонстрирует то же поведение, что и на VС++ и g++.

Конечно, удаление указателя NULL является no-op, поэтому компилятору не нужно вызывать оператор delete. Если на самом деле выделяется объект:

    Widget* w = new Widget;

тогда все работает как ожидалось.

Ответ 5

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

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

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

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

Ответ 6

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

  • не вызывает деструктор, которому нужен экземпляр
  • останавливает операцию удаления (вид оптимизации скорости IMHO).

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

Ответ 7

Вы пытались удалить указатель 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; 
}

Вывод:

новый оператор Widget()
~ Widget()
оператор delete