Во-первых, действительно существует несколько различных операторов new
и delete
(на самом деле произвольное число).
Во-первых, существует ::operator new
, ::operator new[]
, ::operator delete
и ::operator delete[]
.Во-вторых, для любого класса X
существует X::operator new
, X::operator new[]
, X::operator delete
и X::operator delete[]
.
Между ними гораздо чаще перегрузить операторы, специфичные для класса, чем глобальныеоператоры - довольно часто при использовании памяти определенного класса следует достаточно конкретному шаблону, чтобы можно было писать операторы, которые обеспечивают существенные улучшения по умолчанию.Как правило, гораздо труднее точно или конкретно предсказать использование памяти в глобальном масштабе.
Вероятно, также стоит упомянуть, что, хотя operator new
и operator new[]
отделены друг от друга (аналогично для любого * 1020)* и X::operator new[]
), нет разницы между требованиями к ним.Один будет вызываться для выделения одного объекта, а другой - для выделения массива объектов, но каждый из них все равно просто получает необходимый объем памяти и должен возвращать адрес блока памяти (по крайней мере) такого большого размера.
Говоря о требованиях, вероятно, стоит рассмотреть другие требования 1 : глобальные операторы должны быть действительно глобальными - вы не можете помещать их в пространство имен или сделать один статический в определенной единице перевода.Другими словами, существует только два уровня, на которых могут иметь место перегрузки: перегрузка для класса или глобальная перегрузка.Промежуточные точки, такие как «все классы в пространстве имен X» или «все распределения в единице перевода Y», не допускаются.Специфичные для класса операторы должны быть static
- но на самом деле вы не обязаны объявлять их как статические - они будут статическими независимо от того, объявите вы их явно static
или нет.Официально глобальные операторы много возвращают память, выровненную так, что она может использоваться для объекта любого типа.Неофициально в этом есть небольшая простор для маневра: если вы получаете запрос для небольшого блока (например, 2 байта), вам действительно нужно предоставить память, выровненную для объекта такого размера, так как вы пытаетесь сохранить там что-то большее.в любом случае это приведет к неопределенному поведению.
Рассмотрев эти предварительные условия, давайте вернемся к первоначальному вопросу о , почему вы захотите перегрузить эти операторы.Во-первых, я должен отметить, что причины перегрузки глобальных операторов, как правило, существенно отличаются от причин перегрузки операторов, специфичных для класса.
Так как это более распространено, я буду говорить о классе, специфичном для класса.операторы в первую очередь.Основной причиной управления памятью для конкретного класса является производительность.Обычно это происходит в одной или двух формах: либо повышение скорости, либо уменьшение фрагментации.Скорость повышается благодаря тому, что диспетчер памяти будет только иметь дело с блоками определенного размера, поэтому он может возвращать адрес любого свободного блока, а не тратить время на проверку, достаточно ли большой блок, расщеплениеблок на две части, если он слишком большой, и т. д. Фрагментация уменьшается (в основном) таким же образом - например, предварительное выделение блока, достаточно большого для N объектов, дает ровно пространство, необходимое для N объектов;выделение памяти для одного объекта выделит ровно места для одного объекта, а не на один байт больше.
Существует гораздо больше причин для перегрузки операторов глобального управления памятью.Многие из них ориентированы на отладку или инструментарий, такой как отслеживание общего объема памяти, необходимой приложению (например, при подготовке к переносу во встроенную систему), или отладка проблем с памятью путем выявления несоответствий между выделением и освобождением памяти.Другой распространенной стратегией является выделение дополнительной памяти до и после границ каждого запрашиваемого блока и запись уникальных шаблонов в эти области.В конце выполнения (и, возможно, в другое время) эти области проверяются, чтобы определить, не был ли код написан за пределами выделенных границ.Еще одно - попытаться улучшить простоту использования за счет автоматизации, по крайней мере, некоторых аспектов выделения или удаления памяти, например, с помощью автоматического сборщика мусора .
глобального распределителя, не используемого по умолчанию можно также использовать для улучшения производительности.Типичным случаем будет замена распределителя по умолчанию, который в целом был просто медленным (например, по крайней мере, некоторые версии MS VC ++ около 4.x будут вызывать системные функции HeapAlloc
и HeapFree
для каждое распределение/ операция удаления).Другая возможность, которую я видел на практике, произошла на процессорах Intel при использовании операций SSE.Они работают на 128-битных данных.Хотя операции будут работать независимо от выравнивания, скорость улучшается, когда данные выровнены по 128-битным границам.Некоторые компиляторы (например, MS VC ++ снова 2 ) не обязательно принудительно выравнивают эту большую границу, поэтому даже если код, использующий распределитель по умолчанию, будет работать, замена выделения может обеспечить существенное улучшение скорости для этих операций..
- Большинство требований покрыто в §3.7.3 и §18.4 стандарта C ++ (или §3.7.4 и §18.6 в C ++ 0x, по крайней мере, на моментN3291).
- Я чувствую себя обязанным отметить, что не собираюсь выбирать компилятор Microsoft - я сомневаюсь, что у него необычное количество таких проблем, но я часто его используюпоэтому я склонен быть в курсе его проблем.