Оператор удаляет подпись непредвиденного поведения

В своей книге "Язык программирования C++" (4-е издание) Страструп упоминает, что глобальный оператор new & delete может быть перегружен путем написания глобальных функций со следующими сигнатурами:

void* operator new(size_t);               // use for individual object
void* operator new[](size_t);             // use for array
void operator delete(void*, size_t);      // use for individual object
void operator delete[](void*, size_t);    // use for array

ПРИМЕЧАНИЕ. Параметр size_t передается для удаления, чтобы определить правильный размер объекта, в частности, при удалении производного объекта, на который указывает базовый указатель (base требуется виртуальный dtor, чтобы был передан правильный размер).

Я пытался перегрузить глобальные версии для отдельного объекта. Оператор новый работает отлично. Оператор delete с указанной подписью работает нормально, но delete не вызывается. Если я изменю подпись удаления так, чтобы она просто заняла void *, она будет вызвана. В чем может быть проблема:

Вот код:

void * operator new (size_t size)
{
    cout << "My operator new called\n";
    auto p = malloc(size);
    return p;
}

void operator delete (void * ptr, size_t size) // Removing size_t parameter makes it work
{
    cout << "My operator delete called\n";
    free(ptr);
}

Странным является также тот факт, что если я заставлю оператора удалить член класса, чтобы его перегружали только для этого класса, обе подписи удаления (с size_t и без size_t), похоже, будут работать!

Передача параметра size_t в delete кажется логичной, как объяснено в примечании, которое я упомянул. Но что может быть причиной такого поведения? Я использую VS2013 для тестирования примеров.

2 ответа

Решение

Из проекта C++1y:

5.3.5 Удалить [expr.delete]

[...]
11 Когда выражение delete выполняется, выбранная функция освобождения должна вызываться с адресом блока памяти, который должен быть возвращен в качестве первого аргумента, и (если используется двухпараметрическая функция освобождения) размером блока в качестве его Второй аргумент.83

Сноска 83) Если статический тип удаляемого объекта завершен и отличается от динамического типа, а деструктор не является виртуальным, размер может быть неправильным, но этот случай уже не определен, как указано выше.

17.6.4.6 Функции замены [replacement.functions]

1 В разделах 18–30 и Приложении D описывается поведение множества функций, определенных стандартной библиотекой C++. Однако при некоторых обстоятельствах некоторые из этих описаний функций также применяются к функциям замены, определенным в программе (17.3).
2 Программа на C++ может предоставить определение для любой из двенадцати сигнатур функции динамического выделения памяти, объявленных в заголовке <new> (3.7.4, 18.6):

operator new(std::size_t)
operator new(std::size_t, const std::nothrow_t&)
operator new[](std::size_t)
operator new[](std::size_t, const std::nothrow_t&)
perator delete(void*)
operator delete(void*, const std::nothrow_t&)
operator delete[](void*)
operator delete[](void*, const std::nothrow_t&)

примечание мной: следующие четыре являются новыми в C++1y

operator delete(void*, std::size_t)
operator delete(void*, std::size_t, const std::nothrow_t&)
operator delete[](void*, std::size_t)
operator delete[](void*, std::size_t, const std::nothrow_t&)

3 Определения программы используются вместо версий по умолчанию, предоставляемых реализацией (18.6). Такая замена происходит до запуска программы (3.2, 3.6). Определения программы не должны быть указаны как встроенные. Диагностика не требуется.

Также взгляните на предложение, которое вводит размер освобождения в C++1y:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3536.html

В C++11 не является членом void operator delete(void*, size_t) это "размещение размещения с дополнительными аргументами". Это соответствует размещению размещения с дополнительными аргументами (если вы определили один): void *operator new(size_t, size_t),

Пояснение этого, согласно 3.7.4.2, T::operator delete(void*, size_t) это обычная функция освобождения, но N3337 не говорит, что ::operator delete(void *, size_t) обычная функция освобождения; на самом деле, что подпись для ::operator delete не появляется нигде в документе. В частности, 17.6.4.6 не перечисляет его среди глобальных версий.

В C++1y, ::operator delete(void*, size_t) это обычная функция освобождения (т.е. не размещение). Мне кажется, в этом переломное изменение между C++11 и C++1y.

Согласно N3797, в C++ 1y если заменить operator delete(void *) тогда вы также должны заменить operator delete(void *, size_t) и наоборот. (В противном случае, предположительно, программа некорректна).

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

Примечание: перегрузка - неправильный термин; когда вы определяете обычный оператор размещения, он заменяет стандартную версию библиотеки.

Другие вопросы по тегам