Разница между delete и operator delete

323
22 декабря 2017, 02:16

В чём разница между этими действиями?

static void operator delete (void *p)  { ::delete p; }
static void operator delete (void *p)  { ::operator delete(p); }

Кажется, что всё работает в обоих случаях: https://ideone.com/cQ8lTJ

#include <iostream>
using namespace std;
struct a { static void operator delete (void *p)  { ::delete p; } };
struct b { static void operator delete (void *p)  { ::operator delete(p); } };
int main()
{
  delete new a();
  delete new b();
  cout << "Done :)" << endl;
  return 0;
}

Но если добраться до предупреждений компилятора https://ideone.com/bN3XOh

prog.cpp: In static member function ‘static void a::operator delete(void*)’:
prog.cpp:5:62: warning: deleting ‘void*’ is undefined [-Wdelete-incomplete]
 struct a { static void operator delete (void *p)  { ::delete p; } };
                                                              ^

то возникает ощущение, что он предупреждает о UB в первом варианте.

Действительно ли это UB?
Если да, то почему это всего лишь предупреждение, а не ошибка?

PS: Из похожего нашёл такой вопрос, но там про внутреннее устройство вызова delete, причём не показывается, почему именно код из моего вопроса неверный.

Answer 1

operator delete - это функция.
::operator delete(p); - это вызов этой функции.

Выражение delete p; - это вызов деструктора, поиск указателя на полный объект и вызов функции operator delete для этого полного объекта.
Операция delete для void* не имеет смысла, о чем и говорит компилятор.
В контексте другой функции operator delete оно особенно не имеет смысла.

Более подробно про выражение delete и функции operator delete можно почитать тут.

Answer 2

В качестве небольшого дополнения к ответу @Abyx:

Согласно стандарту, 8.5.2.5/1:

The operand shall be of pointer to object type or of class type. If of class type, the operand is contextually implicitly converted to a pointer to object type.⁸²

⁸²⁾ This implies that an object cannot be deleted using a pointer of type void* because void is not an object type.

Таким образом, использование ::delete p; (первый вариант) запрещено стандартом, и ведёт таким образом к UB. Почему это объявлено UB, а не ошибкой компиляции, мне сложно судить.

Ещё одна причина, по которой такой вызов проблематичен: (стандарт, 8.5.2.5/1):

In a single-object delete expression, if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

Answer 3

Немного кода для иллюстрации ответа @Abyx'а

#include <iostream>
#include <string>
using namespace std;
class Foo {
public:
    Foo (const std::string &_str): str(_str) {
        std::cout << str << "::" << __func__<< '\n';
    }
    ~Foo () { std::cout << str << "::" << __func__<< '\n'; }
    static void* operator new (size_t sz) {
          std::cout << __func__<< " sz(" << sz << ')' << '\n';
          return ::operator new(sz);
    }
    static void operator delete (void* p) {
          std::cout << __func__ << '\n'; return ::operator delete(p);
    }
    std::string str;
};
int main(int /*argc*/, char* /*argv*/[])
{
    Foo *foo = new Foo("foo1");
    delete foo;
    std::cout << '\n';
    foo = new Foo("foo2");
    Foo::operator delete(foo);

    return 0;
}

Вывод:

operator new sz(32)
foo1::Foo
foo1::~Foo
operator delete
operator new sz(32)
foo2::Foo
operator delete

т.е. Как и ожидается operator delete не вызывает деструктор.

Answer 4

::delete p; и ::operator delete(p); - две альтернативных записи вызова глобального оператора delete.

А предупреждение компилятора справедливо вызвано попыткой удаления указателя на незавершенный тип (incomplete type). Однако неопределенное поведение конкретно в этом случае заключается не в вызове delete именно для типа void* (хотя он и является незавершенным), а в том, что тип, на который указывает указатель, не является непосредственно типом или одним из подтипов типа, который был и использован при вызове оператора new, создавшего этот объект. Причем диагностического сообщения в этом случае вообще не требуется. Скорее всего, в компиляторе еще не реализовали дополнительные диагностики для вызова delete с использованием синтаксиса функции, так как такое встречается сравнительно редко.

READ ALSO
DCE/RPC - узнать переменные окружения или расположения системных папок

DCE/RPC - узнать переменные окружения или расположения системных папок

Можно ли с помощью DCE/RPC узнать нужные переменные окружения или пути к системных папкам, таким как c:program file или home user directory?

227
Количество аргументов функции

Количество аргументов функции

Недавно был вопрос о том, как объявить функцию с n аргументами заданного типаУ меня возник противоположный вопрос

247
Реализация поиска простых делителей

Реализация поиска простых делителей

Всем привет, написал код по поиску простых чисел и запоминания этих чисел и их степеней:

293