Прежде всего, это действительно можно упростить до 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 ++. Просто оба компилятора проводят здесь одну и ту же оптимизацию.