Часть I
В этой записи часто задаваемых вопросов по C++ объясняется почему< /em> можно перегрузить операторы new
и delete
для собственного класса. Этот настоящий FAQ пытается объяснить, как это сделать в соответствии со стандартом.
Реализация пользовательского оператора new
Стандарт C++ (§18.4.1.1) определяет operator new
как:
void* operator new (std::size_t size) throw (std::bad_alloc);
Стандарт C++ определяет семантику, которой должны подчиняться пользовательские версии этих операторов в §3.7.3 и §18.4.1.
Резюмируем требования.
Требование 1. Он должен динамически выделять не менее size
байт памяти и возвращать указатель на выделенную память. Цитата из стандарта С++, раздел 3.7.4.1.3:
Функция выделения пытается выделить запрошенный объем памяти. В случае успеха он должен вернуть адрес начала блока памяти, длина которого в байтах должна быть не меньше запрошенного размера...
Стандарт также требует:
... Возвращенный указатель должен быть соответствующим образом выровнен, чтобы его можно было преобразовать в указатель любого полного типа объекта, а затем использовать для доступа к объекту или массиву в выделенном хранилище (до тех пор, пока хранилище не будет явно освобождено вызовом соответствующего функция освобождения). Даже если размер запрошенного пространства равен нулю, запрос может завершиться ошибкой. Если запрос выполнен успешно, возвращаемое значение должно быть ненулевым значением указателя (4.10) p0, отличным от любого ранее возвращенного значения p1, если только это значение p1 не было впоследствии передано оператору delete
.
Это дает нам дополнительные важные требования:
Требование 2. Используемая нами функция выделения памяти (обычно malloc()
или какой-либо другой пользовательский распределитель) должна возвращать соответствующим образом выровненный указатель на выделенную память, который можно преобразовать в указатель полного типа объекта и используется для доступа к объекту.
Требование 3. Наш пользовательский оператор new
должен возвращать допустимый указатель, даже если запрашиваются нулевые байты.
Одно из очевидных требований, которое можно вывести даже из прототипа new
:
Требование №4. Если new
не может выделить динамическую память запрошенного размера, то должно быть выдано исключение типа std::bad_alloc
.
Но! Это нечто большее, чем кажется на первый взгляд: если вы внимательно посмотрите на оператор new
документация (цитата из стандарта следует ниже), в нем говорится:
Если для определения < strong>new_handler, эта new_handler
функция вызывается стандартное определение по умолчанию для operator new
, если он не может выделить запрошенное хранилище самостоятельно.
Чтобы понять, как наш пользовательский new
должен поддерживать это требование, мы должны понять:
Что такое new_handler
и set_new_handler
?
new_handler
— это typedef для указателя на функцию, которая ничего не принимает и не возвращает, а set_new_handler
— это функция, которая принимает и возвращает new_handler
.
Параметр set_new_handler
является указателем на функцию, которую оператор new должен вызывать, если не может выделить запрошенную память. Его возвращаемое значение является указателем на ранее зарегистрированную функцию-обработчик или null, если предыдущего обработчика не было.
Подходящий момент для примера кода, чтобы прояснить ситуацию:
#include <iostream>
#include <cstdlib>
// function to call if operator new can't allocate enough memory or error arises
void outOfMemHandler()
{
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main()
{
//set the new_handler
std::set_new_handler(outOfMemHandler);
//Request huge memory size, that will cause ::operator new to fail
int *pBigDataArray = new int[100000000L];
return 0;
}
В приведенном выше примере operator new
(скорее всего) не сможет выделить место для 100 000 000 целых чисел, и будет вызвана функция outOfMemHandler()
, и программа прервется через выдает сообщение об ошибке.
Здесь важно отметить, что когда operator new
не может выполнить запрос памяти, он многократно вызывает функцию new-handler
до тех пор, пока не найдется достаточно памяти или пока не закончатся новые обработчики. В приведенном выше примере, если мы не вызовем std::abort()
, outOfMemHandler()
будет вызываться повторно. Следовательно, обработчик должен либо убедиться, что следующее выделение прошло успешно, либо зарегистрировать другой обработчик, либо не зарегистрировать обработчик, либо не возвращаться (т. е. завершать программу). Если нового обработчика нет и выделение не удалось, оператор выдаст исключение.
Продолжение 1
person
Alok Save
schedule
25.08.2011
c++-faq
не для всех вопросов и ответов в книжном стиле, которые может придумать обычный пользователь. - person Lightness Races in Orbit   schedule 25.08.2011c++-faq
был просто первым и лучшим костылем, который появился, предназначенным для временное решение, пока мы не нашли лучшее. Я был бы очень рад, если бы появились новые идеи о том, как это сделать, и их можно было бы обсудить. - person sbi   schedule 25.08.2011