Оператор размещения new
по умолчанию объявлен в 18.6 [support.dynamic] ¶1 с несинхронизирующей спецификацией исключений:
void* operator new (std::size_t size, void* ptr) noexcept;
Эта функция ничего не делает, кроме return ptr;
, поэтому разумно, чтобы она была noexcept
, однако согласно 5.3.4 [expr.new] ¶15 это означает, что компилятор должен проверить, что он не возвращает значение null до вызов конструктора объекта:
-15-
[Примечание: если функция распределения не объявлена с исключительной спецификацией исключения (15.4), это указывает на невозможность выделения хранилища путем выброса исключенияstd::bad_alloc
(пункт 15, 18.6.2.1); он возвращает ненулевой указатель иначе. Если функция распределения объявлена с несинхронизирующей спецификацией исключений, она возвращает значение null, чтобы указывать на неспособность выделить хранилище и ненулевой указатель в противном случае. -end note] Если функция распределения возвращает значение null, инициализация не выполняется, функция освобождения не должна вызываться, а значение нового выражения должно быть нулевым.
Мне кажется, что (в частности, для размещения new
, а не вообще) эта нулевая проверка является неудачным хитом производительности, хотя и небольшим.
Я отлаживал некоторый код, в котором место размещения new
использовалось в кодах с очень высокой степенью производительности, чтобы улучшить генерацию кода компилятора, и проверка на нуль была обнаружена в сборке. Предоставляя перегрузку размещения new
для класса, объявленную с помощью спецификации исключения метаданных (даже если она не может быть выбрана), условная ветвь была удалена, что также позволило компилятору генерировать меньший код для окружающих встроенных функций, Результат сказать, что функция размещения new
могла бы бросить, даже если она не могла, была значительно лучше кода.
Итак, мне стало интересно, действительно ли нулевая проверка для случая размещения new
. Единственный способ вернуть null - это передать его null. Хотя это возможно и, по-видимому, законно, написать:
void* ptr = nullptr;
Obj* obj = new (ptr) Obj();
assert( obj == nullptr );
Я не понимаю, почему это было бы полезно, я предлагаю, было бы лучше, если бы программисту пришлось явно проверить нуль, прежде чем использовать размещение new
например.
Obj* obj = ptr ? new (ptr) Obj() : nullptr;
Кто-нибудь когда-либо нуждался в размещении new
, чтобы правильно обрабатывать случай с нулевым указателем? (т.е. без добавления явной проверки, что ptr
является допустимой ячейкой памяти.)
Мне интересно, было бы разумно запретить передачу нулевого указателя на функцию размещения по умолчанию new
, а если нет, есть ли лучший способ избежать ненужной ветки, кроме попытки сказать компилятору, значение не равно нулю, например
void* ptr = getAddress();
(void) *(Obj*)ptr; // inform the optimiser that dereferencing pointer is valid
Obj* obj = new (ptr) Obj();
Или:
void* ptr = getAddress();
if (!ptr)
__builtin_unreachable(); // same, but not portable
Obj* obj = new (ptr) Obj();
N.B. Этот вопрос намеренно помечен микро-оптимизацией, я не, предлагая вам перегружать место размещения new
для всех ваших типов, чтобы "улучшить" производительность. Этот эффект был замечен в очень специфическом критически важном случае и основан на профилировании и измерении.
Обновление: DR 1748 делает поведение undefined для использования нулевого указателя с новым местом размещения, поэтому компиляторы больше не требуются сделайте чек.