Почему :: operator new [] необходимо, когда :: operator new достаточно?

Как известно, стандарт C++ определяет две формы глобальных функций распределения:

void* operator new(size_t);
void* operator new[](size_t);

Кроме того, в проекте C++ стандарта (18.6.1.2 n3797) говорится:

227). Не несет ответственности оператора new или оператора delete, чтобы отметить количество повторений или размер элемента массива. Эти операции выполняются в другом месте в новых и удаленных выражениях массива. Новое выражение массива может, однако, увеличить аргумент размера для оператора new, чтобы получить пространство для хранения дополнительной информации.

Меня смущает:

Что делать, если мы удалим void* operator new[](size_t); от стандарта, и просто использовать void* operator new(size_t) вместо этого? Какое обоснование для определения избыточной глобальной функции распределения?

Ответы

Ответ 1

Я думаю, что ::operator new[] может быть полезен для довольно специализированных систем, где "большие, но мало" массивы могут выделяться другим распределителем, чем "маленькие, но многочисленные" объекты. Однако в настоящее время это что-то вроде реликвии.

operator new может разумно ожидать, что объект будет построен по возвращенному точному адресу, но operator new[] не может. Первые байты блока выделения могут использоваться для "cookie" размера, массив может быть редко инициализирован и т.д. Различие становится более значимым для operator new члена operator new, который может быть специализирован для своего конкретного класса.

В любом случае ::operator new[] не может быть очень существенным, потому что std::vector (через std::allocator), который в настоящее время является самым популярным способом получения динамических массивов, игнорирует его.

В современных C++ пользовательские распределители обычно являются лучшим выбором, чем operator new пользовательский интерфейс. Фактически, new выражения следует избегать полностью в пользу классов контейнера (или смарт-указателя и т.д.), Которые обеспечивают большую безопасность исключений.

Ответ 2

Стандарт (n3936) дает понять, что эти два оператора обслуживают разные, но связанные с ними цели.

operator new вызывает функцию void* operator new(std::size_t). Первый аргумент должен быть идентичен аргументу оператора. Он возвращает блок хранения, соответствующим образом выровненный, и который может быть несколько больше, чем требуется.

operator new[] вызывает функцию void* operator new[](std::size_t). Первый аргумент может быть больше аргумента, предоставленного оператору, чтобы предоставить дополнительное пространство для хранения, если это необходимо для индексирования массива. По умолчанию для обоих предусмотрено просто вызвать malloc().

Цель operator new[] - поддерживать специализированную индексацию массивов, если она доступна. Он не имеет ничего общего с пулами памяти или чем-то еще. В соответствующей реализации, которая использовала эту функцию, реализация создавала специализированные таблицы в дополнительном пространстве, а компилятор создавал код для инструкций или звонков в подпрограммы поддержки библиотек, которые использовали эти таблицы. C++ с использованием массивов и невозможностью использования новых [] не будет работать на этих платформах.

Я лично не знаю о такой реализации, но он похож на функции, необходимые для поддержки определенных мэйнфреймов (CDC, IBM и т.д.), Которые имеют архитектуру, совершенно не похожую на чипы Intel или RISC, которые мы знаем и любим.

На мой взгляд, принятый ответ неверен.


Только для полноты стандарт (n3936 в основном в S5.3.4) содержит следующее.

  1. Различие между распределением "объекта массива" или "объекта без массива"
  2. Ссылки на "служебные данные распределения массива", подразумевая, что может потребоваться дополнительное хранилище, и может (каким-то образом) использоваться для подсчета повторений или размера элемента.

Нет ссылок на пулы памяти или какой-либо намек на то, что это может быть рассмотрено.

Ответ 3

::operator new[] и ~ delete[] облегчить отладку использования памяти, являясь центральным пунктом для операций распределения и освобождения от аудита; вы можете затем обеспечить, чтобы форма массива использовалась как для обоих, так и для них.

Есть также много правдоподобных, если очень необычная/грубая настройка использует:

  • выделять массивы из отдельного пула, возможно потому, что это существенно улучшило средние хиты кэша для небольших объектов с динамическим распределением с одним объектом,

  • различные подсказки доступа к памяти (ala madvise) для данных массива/не-массива

Все это немного странно и вне повседневных проблем 99,999% программистов, но почему это мешает?

Ответ 4

Я уверен, что есть правильные варианты использования, требующие отдельного new[] и new, но я еще не встречался, но это уникально возможно с этим разделением и ничем другим.

Однако я вижу это так: поскольку пользователь вызывает разные версии оператора new, стандарт C++ был бы виновен в бессмысленном и умышленном потере информации, если бы они определили только одного operator new и имели как new и new[] вперед. Здесь есть (буквально) один бит информации, который может быть полезен кому-то, и я не думаю, что люди в комитете могли бы выбросить его с чистой совестью!

Кроме того, необходимость реализовать дополнительный new[] является очень незначительным неудобством для всех нас, если вообще, поэтому компромисс с сохранением одного бита информации выигрывает от того, чтобы реализовать одну простую функцию в небольшой доле наших программ.

Ответ 5

C++ Язык программирования: Special Edition p 423 говорит:

Функции operator new() и operator delete() позволяют пользователю выполнять распределение и освобождение отдельных объектов; operator new[]() и operator delete[]() выполняют точно ту же роль для распределения и освобождения массивов.

Спасибо Tony D за исправление моего непонимания этого нюанса.

Ничего себе, это не часто я поймал что-то в C++, я так уверен - я, должно быть, проводил слишком много времени в Objective-C!

оригинальный неправильный ответ

Это просто - новая [] форма вызывает конструктор на каждом элементе классического массива C.

Таким образом, он сначала выделяет пространство для всех объектов, а затем итерации вызывает конструктор для каждого слота.