Для чего нужен второй параметр у оператора operator delete?

87
14 января 2021, 23:40

Есть ли разница в объявлении operator delete(void*) и operator delete(void*, size_t) для класса? Нужен ли на самом деле второй параметр? И если да, то зачем?

Answer 1

Наличие второго опционального параметра типа size_t в функции operator delete тянется еще со времен "классического" C++, т.е. C++98, где такой параметр мог использоваться в operator delete, перегруженных именно для индивидуальных классов. То есть внутри конкретного класса вы можете на выбор объявлять

operator delete(void *p);
// или 
operator delete(void *p, size_t s);

В ситуации, когда удаляемый класс предоставляет свой собственный перегруженный operator delete со вторым параметром типа size_t, компилятор обязан передать в такой оператор корректный размер удаляемого блока памяти, т.е. тот же размер, что передавался в operator new при выделении этого блока.

Для глобального operator delete такой возможности не предоставлялось.

Эта возможность предусмотрена в языке из-за того, что перегруженные operator new и operator delete базового класса могут использоваться для выделения/освобождения памяти для объектов производных классов

struct Base
{
  void *operator new(size_t s) 
  { 
    std::cout << "new " << s << std::endl;
    return ::operator new(s); 
  }
  void operator delete(void *p, size_t s) 
  { 
    std::cout << "delete " << s << std::endl;
    ::operator delete(p);
  }
};
struct Derived : Base
{
  char buffer[1024];
};
int main()
{
  Base *pb = new Base;
  delete pb;
  Derived *pd = new Derived;
  delete pd;
}

При помощи анализа передаваемого в operator new и operator delete значения эти операторы могут определять, какой из возможных размеров выделяется или освобождается и соответствующим образом перенаправлять вызовы.

Для глобальных функций ::operator new и ::operator delete такой возможности в C++98 не было, т.е. глобально предоставлялся только замещаемый

operator delete(void *p);

Однако начиная с С++14 такая возможность была предоставлена и для глобальных функций тоже. Это было сделано для улучшения поддержки аллокаторов, которые не хранят размер блока в самом блоке (или где-то рядом), т.е. для аллокаторов которым трудно определить размер блока по указателю. Например, для пулов одноразмерных объектов. Также такая возможность может быть полезна для оптимизации выделения/освобождения памяти в появившейся в C++14 возможности расширенных выделений памяти, когда два или более соседних new-выражения обходятся одним вызовом operator new с суммарным размером (и, соответственно, симметричной обработкой delete-выражений).

То есть если вы по какой-то причине хотите получать в ::operator delete тот же самый размер, что передавался в соответствующий вызов ::operator new, то объявляйте свой ::operator delete со вторым параметром типа size_t. Если вас не интересует этот размер, то объявляйте его без такого параметра.

Ситуация с наличием сразу двух вариантов operator delete, похоже, находится в подвешенном состоянии уже давно и является дефектом стандарта. Скорее всего дело закончится тем, что предоставление сразу двух вариантов будет приводить к ошибке неоднозначности.

Answer 2

Оператор вызывается, если программист хочет вручную заниматься выделением памяти где-нибудь. (скрытно/в файлах/с обнулением) При вызове удаления объекта будет вызываться пользовательский delete с аргументом размера памяти.

// > g++-5 -Wall -Wpedantic -std=c++14 operdelet.cpp
# include <iostream>
# include <string.h>
class A{
int i[10];
public:
void    operator delete[](void * p,size_t s){
    std::cout<<"A:s="<<s<<std::endl;
    memset(p, 666,s); // зачищаю память
    :: operator delete[]( p ); }
};
class B{
int i[100];
public:
virtual ~B(){}
void    operator delete(void * p,size_t s);
};
class C:public B{
  int i[1000];
public:
  virtual ~C(){}
};
void   B:: operator delete(void * p,size_t s){
    std::cout<<"sizeof(B)="<<sizeof(B)<<std::endl;
    std::cout<<"sizeof(C)="<<sizeof(C)<<std::endl;
    std::cout<<"B:s="<<s<<std::endl;
    memset(p, 555,s); // зачищаю память
    :: operator delete( p ); }
void    operator delete(void * p,size_t s){
    std::cout<<"main:s="<<s<<std::endl;
    memset(p, 777,s); // зачищаю память
    :: operator delete( p ); }
int main() {
    A * a = new A;
    B * b = new B;
    A * a7 = new A[7];
    B * c = new C ;
    delete c ;
    delete [] a7 ;
    delete b;
    delete a; }

Сначала вызывается этот оператор, дальше я вызываю стандартное освобождение.

sizeof(B)=408
sizeof(C)=4408
B:s=4408
A:s=288
sizeof(B)=408
sizeof(C)=4408
B:s=408
main:s=40

Первый вызов удаления с виртуальным деструктором показывает, что оператор delete из базового класса не знает какой родственный класс будет удалён, поэтому размер подскажет, что дальше делать.

Второе удаление массива классов показывает, что память выделенная под массив занимает семь элементов плюс размер массива (size_t). Оператор delete не знает ни размера массива ни дополнительных проблем, что придумал данный компилятор. По-этому для ручного распределения памяти для массивов размер памяти очень важно знать.

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

Глобальный оператор delete универсален для всех объектов, и размер нужно знать , сколько нужно освобождать.

READ ALSO
В iOS не кликабельны input в попап окне fancybox

В iOS не кликабельны input в попап окне fancybox

Обнаружилось, что на iOS устройствах в попап окне Fancybox 3 не кликабельны input поля, те

115
Заменить ссылки на странице

Заменить ссылки на странице

Есть много ссылокНужно дописать им якорь на другую страницу

116
Internet Explorer 8 не вызывается jquery ajax success callback

Internet Explorer 8 не вызывается jquery ajax success callback

Добавляю совместимость интернет-магазина с Internet Explorer 8, для этого использую версию JQuery 110

125
Как построить сетку с одним большим элементом?

Как построить сетку с одним большим элементом?

Подскажите, как реализовать такую сетку?

89