Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Старый способ 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.
 }

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

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

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

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

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

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

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

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

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

Я видел, что это использовалось как небольшое снижение производительности для " динамического типа " указатель (в разделе "Под капотом"):

  

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

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

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

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

Когда вы это сделаете:

vector<Foo> vec;

// Allocate memory for a thousand Foos:
vec.reserve(1000);

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

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

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

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

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

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

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

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

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

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

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

Это может быть удобно при использовании разделяемой памяти, среди прочего ... Например: http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_conite_conject_conject_conject_conject / а>

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

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

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

См. файл fp.h в проекте xll по адресу http://xll.codeplex.com . «неоправданная болтовня с компилятором»; проблема для массивов, которым нравится носить с собой свои размеры.

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

Вот убийственное использование для конструктора на месте 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's compliment trick that works by masking off
the desired number in 2's 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 ();

Теперь разве это не вызывает улыбку на лице (:-). I & # 9829; & # 9829; & # 9829; C ++ 1x

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top