Как я мог разумно перегрузить оператор размещения новых? - PullRequest
20 голосов
/ 09 сентября 2010

C ++ допускает перегрузку operator new - как глобального, так и для класса - обычно operator new, operator new[] используется с оператором new[] и размещением operator new отдельно.

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

//from new.h
inline void* operator new( size_t, void* where )
{
   return where;
}

Что еще это могло сделать? Почему и как я мог разумно перегрузить размещение operator new?

Ответы [ 9 ]

16 голосов
/ 09 сентября 2010

Правильный ответ: Вы не можете заменить размещение оператора новым .

§18.4. 1.3 Формы размещения
Эти функции зарезервированы, программа на C ++ может не определять функции, которые заменяют версии в стандартной библиотеке C ++.

Обоснование: единственная цель операторов выделения и освобождения состоит в том, чтобы выделять и освобождать память, поэтому при наличии памяти больше ничего делать не нужно. (Стандарт особо отмечает, что эти функции «Преднамеренно не выполняют никаких других действий».)

4 голосов
/ 09 сентября 2010

Технически, размещение operator new - это любое operator new, которое принимает дополнительные аргументы помимо размера необходимой памяти.

Таким образом, new(std::nothrow) X использует размещение operator new и new(__FILE__, __LINE__) X.

Единственной причиной переопределения operator new(size_t, void*) может быть добавление информации о трассировке, но я думаю, что потребность в этом будет довольно низкой.

3 голосов
/ 09 сентября 2010

Один пример приведен в FAQ Страуструпа.

1 голос
/ 04 марта 2015

Определить свое собственное управление памятью для зарезервированной области - одно хорошее применение.

Иметь другое представление об одних и тех же физических данных (не нужно перемещать данные) - это другое взаимное использование. Это также позволяет вам читать структурированный файл как символы в буфере, а затем накладывать их логическую структуру, определяя объект этого класса над буфером. Сочетание этой вещи с отображением памяти в файлах может обеспечить значительное улучшение производительности. Память отображена аппаратно ... Итак, тысячи заявок!

1 голос
/ 09 сентября 2010

Самым очевидным переопределением было бы копирование этой реализации.

Другим разумным вариантом было бы добавить несколько проверок (например, проверить, что в зоне запроса нет "привязанного маркера").

Я думаю, однако, что смысл - это больше, чем вы должны переопределить его, как только вы переопределите другие (для данного класса), из-за механики имени посмотрите (или не переопределите его, чтобы предотвратитьиспользовать, это тоже хорошо, но это сознательное решение).

0 голосов
/ 03 октября 2014

Наиболее важной дополнительной функциональностью для размещения новой перегрузки будет проверка выравнивания адресов.

Например, предположим, что для некоторого класса требуется выравнивание по 16 байтов.Разработчик перегружает new, new [], delete и delete [] - просто чтобы убедиться, что все выровнено правильно.

Все отлично работает до того момента, когда он пытается использовать свой класс с библиотекой, которая использует размещение new ..Библиотека не имеет представления, требуется ли / какое выравнивание для класса, и адрес, который она пытается «разместить», объект не должен быть выровнен - ​​большой бум.

Самый простой пример такой ситуации - попробуйте использовать std:: vector , где T требует нестандартного выравнивания.

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

0 голосов
/ 19 сентября 2013

Я не совсем уверен в этом вопросе, но следующее переопределение размещения нового на уровне класса:

struct Bar {
void* operator new(size_t /* ignored */, void* where) throw() { return where; }
};

int main() {
  char mem[1];
  Bar* bar = new(mem) Bar;
}

Я считаю, что это допустимый C ++ (и компилируется и работает нормально с gcc 4.4.6).

Вы можете по своему усмотрению изменять реализацию этого оператора (включая удаление предложения throw(), что будет означать, что компилятор больше не проверяет указатель where на ноль перед вызовом конструктора).Проходите осторожно, хотя.

§18.4. 1.3 интересно.Я считаю, что это относится только к новой функции глобального оператора, а не к классу.

0 голосов
/ 09 сентября 2010

Я видел пример, где new-аргумент с двумя аргументами был перезаписан для возврата блоков памяти, предварительно заполненных символом, переданным в качестве дополнительного аргумента. Я не помню, какой исходный код использовался (возможно, memset ()), но функционально это было примерно так:

#include <iostream>
#include <algorithm>
#include <new>
void* operator new [](size_t n, char c)
{
        char* p = new char[n];
        std::fill(p, p+n, c);
        return p;
}
int main()
{
        char* p = new('a') char[10];
        std::cout << p[0] << p[1] << ".." << p[9] << '\n';
}

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

0 голосов
/ 09 сентября 2010

Мое основное использование - создание большого массива объектов.Его производительность намного лучше и имеет меньше накладных расходов для выделения памяти в целом блоке, то есть с использованием VirtualAlloc из Win32 (при программировании окон).Затем вы просто передаете ptr в пределах этого блока каждому новому объекту размещения, например:

char *cp = new char[totalSize];

for(i = 0; i < count; i++, cp += ObjSize)        
{                                                        
    myClass *obj = new(cp) myClass;             
}
...