Какие варианты использования для "размещения новых"? - PullRequest
370 голосов
/ 21 октября 2008

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

Ответы [ 22 ]

337 голосов
/ 21 октября 2008

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

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

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

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

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

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

56 голосов
/ 21 октября 2008

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

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, ...);

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

49 голосов
/ 21 октября 2008

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

32 голосов
/ 21 октября 2008

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

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

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

23 голосов
/ 21 октября 2008

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

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

12 голосов
/ 16 июля 2010

Head Geek: БИНГО! Вы получили это полностью - это именно то, для чего это идеально. Во многих встроенных средах внешние ограничения и / или сценарий общего использования вынуждают программиста отделить выделение объекта от его инициализации. С ++, объединившись, называет это «созданием экземпляра»; но всякий раз, когда действие конструктора должно быть явно вызвано БЕЗ динамического или автоматического выделения, размещение нового - способ сделать это. Это также идеальный способ найти глобальный объект C ++, прикрепленный к адресу аппаратного компонента (ввод-вывод с отображением в памяти) или для любого статического объекта, который по какой-либо причине должен находиться по фиксированному адресу. *

11 голосов
/ 23 мая 2013

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

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

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

9 голосов
/ 04 апреля 2013

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

Старый способ C использовал memset() для установки всех элементов на 0. Вы не можете сделать это в C ++ из-за 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's content.
 }
8 голосов
/ 05 апреля 2013

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

8 голосов
/ 01 октября 2015

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

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

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

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

...