Часть I
В этой статье C ++ FAQ объяснено почему может потребоваться перегрузить операторы new
и delete
для своего собственного класса.В данном часто задаваемом вопросе делается попытка объяснить, как можно сделать стандартным образом.
Реализация пользовательского 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
байтов памяти и возвращать указатель на выделенную память.Цитата из стандарта C ++, раздел 3.7.4.1.3:
Функция выделения пытается выделить запрошенный объем памяти.Если он успешен, он должен вернуть адрес начала блока памяти, длина которого в байтах должна быть, по крайней мере, такой же, как запрашиваемый размер ...
Стандарт дополнительно налагает:
... Возвращаемый указатель должен быть соответствующим образом выровнен, чтобы его можно было преобразовать в указатель любого полного типа объекта и затем использовать для доступа к объекту или массиву в выделенном хранилище (пока хранилище не будетявно освобождается путем вызова соответствующей функции освобождения).Даже если размер запрошенного пространства равен нулю, запрос может завершиться ошибкой.Если запрос выполнен успешно, возвращаемое значение должно быть ненулевым значением указателя (4.10) p0, отличным от любого ранее возвращенного значения p1, если только это значение p1 не было впоследствии передано оператору delete
.
Это дает нам дополнительные важные требования:
Требование № 2: Используемая нами функция выделения памяти (обычно malloc()
или какой-либо другой пользовательский распределитель) должна возвращать соответственновыровненный указатель на выделенную память, который можно преобразовать в указатель полного типа объекта и использовать для доступа к объекту.
Требование № 3: Наш пользовательский оператор new
должен возвращать допустимый указатель, даже когда запрашивается ноль байтов.
Одно из очевидных требований, которые можно вывести из прототипа new
:
Требование № 4: Если new
не может выделить динамическую память запрошенного размера, он должен выдать исключение типа std::bad_alloc
.
Но! Есть еще кое-что дляэто то, что бросается в глаза: если вы внимательно посмотрите на оператор new
документация (цитата из стандарта следует далее), то в нем говорится:
Если set_new_handler был использован для определения функции new_handler , эта функция new_handler
вызывается стандартным определением по умолчанию operator new
, если ононе может выделить запрошенное хранилище самостоятельно.
Чтобы понять, как наш пользовательский new
должен поддерживать это требование, мы должны понять:
Что такое new_handler
и set_new_handler
?
new_handler
- это typedef для указателя на функцию, которая ничего не принимает и не возвращает, а set_new_handler
- это функция, которая принимает и возвращает new_handler
.
set_new_handler
- указатель на функцию, которую должен вызывать оператор new, если он не может выделить запрошенную память.Его возвращаемое значение - указатель на ранее зарегистрированную функцию-обработчик или ноль, если не было предыдущего обработчика.
Удачный момент для примера кода, чтобы прояснить ситуацию:
#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;
}
InВ приведенном выше примере operator new
(наиболее вероятно) не сможет выделить место для 100 000 000 целых чисел, и будет вызвана функция outOfMemHandler()
, и программа прекратит работу после , выдавшей сообщение об ошибке . * 1106.*
Здесь важно отметить, что когда operator new
не может выполнить запрос памяти, он несколько раз вызывает функцию new-handler
, пока не сможет найти достаточно памяти или больше не будет новых обработчиков. В приведенном выше примере, если мы не вызовем std::abort()
, outOfMemHandler()
будет , который будет вызываться повторно . Следовательно, обработчик должен либо гарантировать, что следующее выделение выполнено успешно, либо зарегистрировать другой обработчик, либо не регистрировать обработчик, или не возвращать (то есть завершать программу). Если нового обработчика нет и распределение завершается неудачно, оператор сгенерирует исключение.
Продолжение 1