2009-07-10 1 views
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. Почему это так?удалить указатель NULL не вызывает перегружен удаления при деструкторе написано

Выход при ~ виджет() записывается

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

Выход при ~ виджет() не написано

оператор новый
Из памяти
оператор удаления

+0

Это странно увлекательный вопрос! Я просто посмотрел в «Эффективный C++» Скотта Мейера (к сожалению, у меня есть только 2-й редактор), и я ничего не мог с этим поделать. –

ответ

21

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

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

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

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

Я помню этот becoz я имел подобную Prob некоторое время назад и сохранили ответ в файл с расширением .txt.

ДОПОЛНЕНО-1:

О я нашел here. Также прочитайте эту ссылку defect report. Итак, ответ Unspecified. Глава 5.3.5/7.

+0

+1 звучит как близко к ответу, как мы получим ... хотя я жду дальнейших событий затаив дыхание. –

+0

Если он должен быть не-оператором, компилятор не может выполнять какой-либо код пользователя, так как этот код может привести к побочным эффектам (т. Е. Делать печатью) – xtofl

3

У меня нет хорошего ответа, но я немного упростил вопрос. Следующий код удаляет оператор нового и обработки исключений:

#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++ и г ++.

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

Widget* w = new Widget; 

тогда все работает должным образом.

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

Выход:

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

+0

Да, но вопрос в том, почему его пользовательское удаление вызывается, когда delete вызывается с NULL? (Но только если класс не имеет деструктора?) –

+1

Вопрос не в том, почему деструктор не вызывается. Вопрос в том, что когда я не предоставляю деструктор (в этом случае компилятор создает его для меня), почему вызывает перегруженное удаление? –

4

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

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

+3

Это не совсем правильно - 'delete' вполне очевидно не может быть выполненный из деструктора, поскольку деструктор будет вызван для объектов этого класса со статическим и автоматическим хранилищем, а вызов 'delete' на них явно нежелателен. Что происходит, так это то, что VC++ генерирует функцию _extra_, называемую «скалярным удалением деструктора», которая сначала вызывает собственно деструктор (который все еще генерируется как другая, отдельная функция), а затем выполняет «delete this». А затем вызывает скаляр, удаляющий деструктор везде, где вы «удаляете» объекты такого типа. –

+0

Спасибо за исправление, Павел. Исправил мой ответ соответственно. – eran

+0

Я думаю, что это правильный ответ, поскольку это подтверждается тем фактом, что если dtor удаляется из виджета, но добавляется объект с dtor, вы получите то же поведение. –

-1

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

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

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

+0

Если он не вызывает деструктор, то такое же поведение должно отображаться, когда я не пишу деструктор (в этом случае компилятор должен создать для меня один). Основной смысл здесь в том, почему поведение различно в 2 случаях. –

+0

Поскольку, когда деструктор не указан, оператор delete непосредственно вызывается (поведение по умолчанию), а когда он указан, есть эта «предварительная проверка» экземпляра Я говорю здесь. Здесь я объясняю случай с деструктором, сравнение подразумевалось - возможно, недостаточно ясно. –

9

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

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

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

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

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

+2

Хорошее объяснение! Imho, есть и третий случай: виртуальный деструктор определен. Это невозможно найти во время выполнения, если указатель равен NULL. Еще одна причина, по которой удаление указателей NULL не может вызвать код деструктора ... – xtofl

3

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

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

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

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

+1

Это определенно просто. –

+0

И хороший момент! – xtofl