Какое использование используется для "размещения нового"?

Кто-нибудь здесь когда-либо использовал С++ "размещение нового"? Если да, зачем? Мне кажется, что это было бы полезно только при отображении на карту памяти.

Ответ 1

Размещение нового позволяет построить объект в памяти, который уже выделен.

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

DevX дает хороший пример:

Стандарт C++ также поддерживает размещение нового оператора, который создает объект в заранее выделенном буфере. Это полезно при создании пула памяти, сборщика мусора или просто, когда производительность и безопасность исключений имеют первостепенное значение (нет опасности сбоя выделения, так как память уже выделена, а создание объекта в предварительно выделенном буфере занимает меньше времени):

char *buf  = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi");    // placement new
string *q = new string("hi");          // ordinary heap allocation

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

Распределение в размещении новых

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

Ответ 2

Мы используем его с пользовательскими пулами памяти. Просто эскиз:

class Pool {
public:
    Pool() { /* implementation details irrelevant */ };
    virtual ~Pool() { /* ditto */ };

    virtual void *allocate(size_t);
    virtual void deallocate(void *);

    static Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ }
};

class ClusterPool : public Pool { /* ... */ };
class FastPool : public Pool { /* ... */ };
class MapPool : public Pool { /* ... */ };
class MiscPool : public Pool { /* ... */ };

// elsewhere...

void *pnew_new(size_t size)
{
   return Pool::misc_pool()->allocate(size);
}

void *pnew_new(size_t size, Pool *pool_p)
{
   if (!pool_p) {
      return Pool::misc_pool()->allocate(size);
   }
   else {
      return pool_p->allocate(size);
   }
}

void pnew_delete(void *p)
{
   Pool *hp = Pool::find_pool(p);
   // note: if p == 0, then Pool::find_pool(p) will return 0.
   if (hp) {
      hp->deallocate(p);
   }
}

// elsewhere...

class Obj {
public:
   // misc ctors, dtors, etc.

   // just a sampling of new/del operators
   void *operator new(size_t s)             { return pnew_new(s); }
   void *operator new(size_t s, Pool *hp)   { return pnew_new(s, hp); }
   void operator delete(void *dp)           { pnew_delete(dp); }
   void operator delete(void *dp, Pool*)    { pnew_delete(dp); }

   void *operator new[](size_t s)           { return pnew_new(s); }
   void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); }
   void operator delete[](void *dp)         { pnew_delete(dp); }
   void operator delete[](void *dp, Pool*)  { pnew_delete(dp); }
};

// elsewhere...

ClusterPool *cp = new ClusterPool(arg1, arg2, ...);

Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...);

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

Ответ 3

Это полезно, если вы хотите отделить выделение от инициализации. STL использует новое размещение для создания элементов контейнера.

Ответ 4

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

То, что я могу сделать, - это предопределить большой объем памяти (достаточно большой, чтобы удержать любое количество того, что может потребоваться классу). Затем, когда я выясню во время выполнения, как построить вещи, размещение new можно использовать для построения объектов прямо там, где я их хочу. Одна из ситуаций, которую я знаю, я использовал, чтобы помочь создать гетерогенный круговой буфер.

Это, конечно, не для слабонервных, а для того, почему они делают синтаксис для него довольно грубым.

Ответ 5

Я использовал его для создания объектов, размещенных в стеке с помощью alloca().

бесстыдная вилка: я писал об этом здесь.

Ответ 6

Head Geek: BINGO! Вы получили это полностью - это то, для чего он идеально подходит. Во многих встроенных средах внешние ограничения и/или сценарий общего использования заставляют программиста отделять выделение объекта от его инициализации. Скомпонованный вместе, С++ называет это "экземпляр"; но всякий раз, когда действие конструктора должно быть явно вызвано без динамического или автоматического распределения, размещение нового - это способ сделать это. Это также идеальный способ найти глобальный объект С++, привязанный к адресу аппаратного компонента (I/O с памятью) или для любого статического объекта, который по какой-либо причине должен находиться по фиксированному адресу.

Ответ 7

Я использовал его для создания класса Variant (т.е. объекта, который может представлять одно значение, которое может быть одним из нескольких разных типов).

Если все типы значений, поддерживаемые классом Variant, являются типами POD (например, int, float, double, bool), то мешающий союз C-стиля достаточно, но если вы хотите, чтобы некоторые из типов значений были Объекты С++ (например, std::string), функция C union не будет выполняться, поскольку типы данных, отличные от POD, не могут быть объявлены как часть объединения.

Поэтому вместо этого я выделяю массив байтов, который достаточно велик (например, sizeof (the_largest_data_type_I_support)) и использует новое размещение для инициализации соответствующего объекта С++ в этой области, когда Variant установлен для хранения значения этого типа. (И размещение удаляется заранее, если вы, конечно, переключитесь с другого типа данных, отличного от POD)

Ответ 8

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

Старый способ C использовал memset(), чтобы установить все элементы в 0. Вы не можете сделать это на С++ из-за vtables и настраиваемых конструкторов объектов.

Поэтому я иногда использую следующие

 static Mystruct m;

 for(...)  {
     // re-initialize the structure. Note the use of placement new
     // and the extra parenthesis after Mystruct to force initialization.
     new (&m) Mystruct();

     // do-some work that modifies m content.
 }

Ответ 9

Это полезно, если вы создаете ядро ​​- где вы помещаете код ядра, который вы читаете с диска или в pagetable? Вы должны знать, куда прыгать.

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

Я также считаю, что в некоторых реализациях STL используется новое размещение, например std::vector. Таким образом, они выделяют место для 2 ^ n элементов и не нуждаются всегда в realloc.

Ответ 10

Размещение new также очень полезно при сериализации (скажем, с boost:: serialization). В 10 лет С++ это только второй случай, когда мне нужно новое место для (третий, если вы включаете интервью:)).

Ответ 11

Используется std::vector<>, потому что std::vector<> обычно выделяет больше памяти, чем objects в vector<>.

Ответ 12

Я думаю, что это не было подчеркнуто ни одним ответом, но еще один хороший пример и использование для нового места размещения - уменьшить фрагментацию памяти (используя пулы памяти). Это особенно полезно для встроенных и высокодоступных систем. В этом последнем случае это особенно важно, потому что для системы, которая должна работать 24/365 дней, очень важно не иметь фрагментации. Эта проблема не имеет ничего общего с утечкой памяти.

Даже когда используется очень хорошая реализация malloc (или аналогичная функция управления памятью), очень сложно иметь дело с фрагментацией в течение длительного времени. В какой-то момент, если вы не умеете эффективно использовать вызовы резервирования/освобождения памяти, вы можете получить много небольших пробелов, которые трудно повторно использовать (назначить новые оговорки). Таким образом, одним из решений, которые используются в этом случае, является использование пула памяти для распределения перед собой памяти для объектов приложения. После каждого раза, когда вам нужна память для какого-либо объекта, вы просто используете новое место для создания нового объекта в уже зарезервированной памяти.

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

Я видел это на практике специально для VXWorks RTOS, поскольку его система распределения памяти по умолчанию много страдает от фрагментации. Поэтому выделение памяти с помощью стандартного метода new/malloc было в основном запрещено в проекте. Все резервирования памяти должны поступать в выделенный пул памяти.

Ответ 13

Я использовал его для хранения объектов с файлами с отображением памяти.
Конкретным примером была база данных изображений, которая обрабатывала большое количество больших изображений (больше, чем могла бы вписываться в память).

Ответ 14

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

Ответ 15

Я видел, что он использовался как незначительный взлом производительности для указателя "динамического типа" (в разделе "Under the Hood" )

Но вот хитроумный трюк, который я использовал для быстрой работы для небольших типов: если удерживаемое значение может помещаться внутри void *, я фактически не хочу выделять новый объект, я заставляю его в самом указателе используя новое размещение.

Ответ 16

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

Возьмите контейнеры, например unordered_map, vector или deque. Все они выделяют больше памяти, чем минимально требуется для вставленных вами элементов, чтобы избежать необходимости распределения кучи для каждой отдельной вставки. Давайте используем vector как самый простой пример.

Когда вы выполните:

<Предварительно > <код > вектор <Foo> VEC; // Выделите память для тысячи Foos: vec.reserve(1000); Код >

... который фактически не создает тысячу Foos. Он просто выделяет/резервирует память для них. Если vector не использовал здесь место размещения, это было бы по умолчанию построение Foos по всему месту, а также необходимость вызывать их деструкторы даже для элементов, которые вы даже не вставляли на первом месте.

Распределение!= Строительство, Освобождение!= Разрушение

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

Между этими идеями должно быть разделение, чтобы избежать излишнего вызова конструкторов и деструкторов излишне левым и правым, и поэтому стандартная библиотека разделяет идею std:: allocator (которая не создавать или уничтожать элементы, когда он выделяет/освобождает память *) вдали от контейнеров, которые его используют, которые вручную создают элементы с помощью размещения новых и вручную уничтожают элементы, используя явные вызовы деструкторов.

  • Я ненавижу дизайн std:: allocator, но это другой предмет, о котором я не буду говорить.:-D

Так или иначе, я, как правило, очень сильно его использую, так как я написал несколько универсальных стандартных контейнеров С++, которые не могут быть построены с точки зрения существующих. Среди них есть небольшая реализация векторов, которую я построил пару десятилетий назад, чтобы избежать распределения кучи в обычных случаях, и эффективного управления памятью (не выделяет один node за раз). В обоих случаях я не мог реализовать их с использованием существующих контейнеров, поэтому мне пришлось использовать placement new, чтобы избежать излишнего вызова конструкторов и деструкторов на ненужные вещи слева и справа.

Естественно, что если вы когда-либо работали с настраиваемыми распределителями для выделения объектов по отдельности, например, для бесплатного списка, то вы также обычно хотели бы использовать placement new, как это (основной пример, который не беспокоит с безопасностью исключения или RAII):

  Foo * foo = new (free_list.allocate()) Foo (...);
...
foo- > ~ Foo();
free_list.free(Foo);
Код>

Ответ 17

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

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

Ответ 19

Одно место, через которое я столкнулся, находится в контейнерах, которые выделяют смежный буфер, а затем заполняют его объектами по мере необходимости. Как уже упоминалось, std::vector может это сделать, и я знаю, что некоторые версии MFC CArray и/или CList сделали это (потому что там, где я впервые столкнулся с ним). Метод перераспределения буфера - очень полезная оптимизация, а размещение нового - это почти единственный способ построить объекты в этом сценарии. Он также иногда используется для создания объектов в блоках памяти, выделенных вне вашего прямого кода.

Я использовал его в аналогичной емкости, хотя он часто не возникает. Это полезный инструмент для инструментария С++.

Ответ 20

Script двигатели могут использовать его в собственном интерфейсе для выделения собственных объектов из скриптов. Для примера см. Angelscript (www.angelcode.com/angelscript).

Ответ 21

Посмотрите файл fp.h в проекте xll в http://xll.codeplex.com Он решает проблему "необоснованного chumminess с компилятором" для массивов, которые как бы нести свои измерения вокруг них.

typedef struct _FP
{
    unsigned short int rows;
    unsigned short int columns;
    double array[1];        /* Actually, array[rows][columns] */
} FP;

Ответ 22

Здесь используется killer для конструктора на месте C++: выравнивание с линией кэша, а также другие полномочия с двумя границами. Вот мой сверхбыстрый алгоритм выравнивания указателя на любую мощность 2 границ с 5 или менее однотактными инструкциями:

/* Quickly aligns the given pointer to a power of two boundary IN BYTES.
@return An aligned pointer of typename T.
@brief Algorithm is a 2 compliment trick that works by masking off
the desired number in 2 compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param boundary_byte_count The boundary byte count that must be an even
power of 2.
@warning Function does not check if the boundary is a power of 2! */
template <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
  uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
  value += (((~value) + 1) & (boundary_byte_count - 1));
  return reinterpret_cast<T*>(value);
}

struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();

Теперь не то, что просто положил улыбку на ваше лицо (:-). я ♥♥♥ C++ 1x