Страницы

Поиск по вопросам

воскресенье, 15 декабря 2019 г.

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

#cpp #language_lawyer #неопределенное_поведение


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

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


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

#include 

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,
причём не показывается, почему именно код из моего вопроса неверный.
    


Ответы

Ответ 1



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

Ответ 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.

Ответ 3



Немного кода для иллюстрации ответа @Abyx'а #include #include 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 не вызывает деструктор.

Ответ 4



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

Комментариев нет:

Отправить комментарий