Кто-нибудь здесь когда-нибудь использовал "новое размещение" C++? Если да, то зачем? Мне кажется, что это будет полезно только на оборудовании с отображением памяти.
Он используется в статья в Википедии о C++ 11 в конструкторе объединения.
@HelloGoodbye, интересно! В статье, которую вы связали, почему вы не можете просто выполнить p = pt и использовать оператор присваивания Point вместо выполнения new(&p) Point(pt)? Интересно, в чем разница между ними. Будет ли первый вызывать operator= в Point, а второй - конструктор копирования Point? но мне до сих пор не очень понятно, почему один лучше другого.
@ Andrei-NiculaePetre Я сам не использовал размещение new, но полагаю, вам следует использовать его вместе с конструктором копирования, если у вас в настоящее время нет объекта этого класса, в противном случае вам следует использовать оператор присваивания копии. Если только класс не является тривиальным; тогда неважно, какой из них вы используете. То же самое и с разрушением объекта. Неспособность обработать это должным образом для нетривиальных классов очень вероятно может привести к странному поведению и даже может вызвать неопределенное поведение в некоторых ситуациях.
@ Andrei-NiculaePetre На самом деле, я считаю пример в статье в Википедии довольно плохим, поскольку он просто предполагает, что предшествующего объекта не существует и что они должны его создать. Это не тот случай, если только что был вызван U::operator=.
@HelloGoodbye Я согласен, в вики-статье это эквивалентно использованию назначения или нового размещения. Я все еще не был уверен, в чем разница между ними, и создал простой код, чтобы увидеть различия, и теперь я вижу размещение new, похожее на std :: move (), оно создает объект в заданном месте. pastebin.com/37mdM0cM и, как вы сказали, это полезно, если у вас еще нет этого объекта, и вы создаете его там на месте, иначе вы могли бы просто использовать присваивание.
@ Andrei-NiculaePetre Размещение нового работает как обычное новое, только не выделяет памяти. Т.е. он также создает новый объект и, таким образом, вызывает конструктор создаваемого класса. Это не имеет ничего общего с std::move (хотя вы все равно можете использовать std::move в конструкторе). Оператор = не вызывает никаких конструкторов и не имеет ничего общего с std::move.
Недавно я использовал новое размещение для моей реализации мемпула. Таким образом, раньше он просто возвращал новый адрес, теперь он создает объект и возвращает адрес.
Вот несколько демонстраций / примеров использования превосходно, чтобы проиллюстрировать, как использовать новое размещение и что оно делает! geeksforgeeks.org/placement-new-operator-cpp
С опозданием на 12 лет, но размещение new позволяет вам сделать эквивалент копировать не копируемые объекты, даже когда memcpy() не может быть использован, потому что это может привести к неопределенному поведению, поскольку тип объекта нетривиально копируем. См. Мой (сверхдлинный) ответ, который я только что добавил: stackoverflow.com/a/63893849/4561887. Новое размещение - это та волшебная недостающая деталь, в которой я отчаянно нуждался и не знал, как получить раньше без memcpy() (но это было неопределенное поведение для моего нетривиально копируемого объекта)!





Это полезно, если вы создаете ядро - где вы размещаете код ядра, который вы читаете с диска, или из таблицы? Вам нужно знать, куда прыгать.
Или в других, очень редких обстоятельствах, например, когда у вас есть много выделенной комнаты и вы хотите разместить несколько структур друг за другом. Их можно упаковать таким образом без использования оператора offsetof (). Однако для этого есть и другие уловки.
Я также считаю, что некоторые реализации STL используют новое размещение, например std :: vector. Таким образом они выделяют место для 2 ^ n элементов, и не нужно всегда перераспределять.
Одной из основных причин его использования является сокращение объема выделяемой памяти, а также такие «уловки», как загрузка объектов с диска.
Я не знаю ни одного ядра, написанного на C++; большинство ядер написано на прямом C.
Операционная система, с которой я изучал основы ОС, написана на C++: sweb.sourceforge.net
Я использовал его для хранения объектов с файлами с отображением памяти. Конкретным примером была база данных изображений, которая обрабатывала очень большое количество больших изображений (больше, чем могло поместиться в памяти).
Это полезно, если вы хотите отделить выделение памяти от инициализации. STL использует новое размещение для создания элементов контейнера.
Он используется std::vector<>, потому что std::vector<> обычно выделяет больше памяти, чем objects в vector<>.
Вы можете сделать это для оптимизации, когда вам нужно создать несколько экземпляров объекта, и быстрее не перераспределять память каждый раз, когда вам нужен новый экземпляр. Вместо этого может быть более эффективным выполнить одно выделение для блока памяти, который может содержать несколько объектов, даже если вы не хотите использовать все сразу.
DevX дает хороший пример:
Standard C++ also supports placement new operator, which constructs an object on a pre-allocated buffer. This is useful when building a memory pool, a garbage collector or simply when performance and exception safety are paramount (there's no danger of allocation failure since the memory has already been allocated, and constructing an object on a pre-allocated buffer takes less time):
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
Вы также можете быть уверены, что не может быть сбоя выделения в определенной части критического кода (например, в коде, выполняемом кардиостимулятором). В этом случае вы захотите выделить память раньше, а затем использовать новое размещение в критическом разделе.
Вы не должны освобождать каждый объект, который использует буфер памяти. Вместо этого вы должны удалить [] только исходный буфер. Затем вам придется вызывать деструкторы ваших классов вручную. Хорошее предложение по этому поводу можно найти в FAQ Страуструпа по адресу: Есть ли «удаление места размещения»?
Но я бы добавил, что в целом это считается устаревшим. Насколько мне известно, он не устарел, потому что его можно использовать для размещения new.
Это не является устаревшим, поскольку вам нужна эта функция для эффективной реализации контейнерных объектов (например, вектора). Если вы не создаете свой собственный контейнер, вам не нужно использовать эту функцию.
Что ж, есть несколько возможных применений, например, при десериализации параметрических неизменяемых типов данных.
Я думаю, точно не устарел.
Также очень важно не забыть #include <memory>, иначе вы можете столкнуться с ужасной головной болью на некоторых платформах, которые не распознают автоматически размещение нового
Строго говоря, это неопределенное поведение - вызывать delete[] в исходном буфере char. Использование размещения new завершило жизненный цикл исходных объектов char за счет повторного использования их хранилища. Если вы теперь вызываете delete[] buf, динамический тип объекта (ов), на который указывает, больше не соответствует их статическому типу, поэтому вы имеете неопределенное поведение. Более последовательно использовать operator new / operator delete для выделения необработанной памяти, предназначенной для использования при размещении new.
Что действительно бесполезно, так это размещение массивов с синтаксисом размещения, см. stackoverflow.com/questions/15254/…
Полезно различать оператор new () и оператор new (). первый выделяет, а второй конструирует.
Я бы определенно не стал использовать кучу в кардиостимуляторе :-)
Это плохой пример предварительного выделения буфера. Показан заголовок фиксированного размера для предварительно выделенного буфера. Буфер все еще находится в куче.
Я счел полезным создать новый экземпляр неизменяемого объекта вместо существующего. В этой ситуации нельзя использовать оператор присваивания копии, потому что существующий объект неизменен. (Это могло быть антипаттерном или неидиоматическим, но у меня это сработало.)
@CharlesBailey UB? Продолжительность жизни? Вы не понимаете.
@RamonZarazua Неправильный заголовок, это #include <new>.
Использование строки в качестве примера - худшее из того, что вы могли сделать, потому что существует мысленный конфликт между людьми, чтобы понять объект управления строкой и последовательность байтов, составляющих содержимое строки (которые все еще помещаются в кучу). А отказ от использования префикса std :: as усугубляет ситуацию и сохраняет антипаттерн «использование std» живым и вредным.
Я просто хочу отметить, что вы также можете предварительно выделить буфер или пул памяти, используя распределение статической памяти, вместо динамического распределения памяти. Пример: в большем объеме, чем в том случае, когда вы будете использовать новое размещение, статически предварительно выделите буфер следующим образом: char buf[sizeof(string)]; вместо этого: char *buf = new char[sizeof(string)];, но используйте это точно так же! Пример: поместите эту строку в этот буфер памяти, используя «размещение нового»: string *p = new (buf) string("hi");. См. Также раздел «Размещение нового» здесь: en.cppreference.com/w/cpp/language/new.
Кроме того, чтобы вызвать деструктор вручную, просто выполните: p->~string();. Или, если вы каким-то образом потеряли дескриптор (указатель) p, вы можете вызвать деструктор вручную непосредственно в буфере, до тех пор, пока вы уверены, что действительный объект в настоящее время все еще находится внутри него, и вы знаете его тип!: ((string*)(&buf[0]))->~string();.
Я изложил свои соображения выше и многие другие в своем ответе здесь: stackoverflow.com/a/63893849/4561887.
Я использовал его для создания объектов, размещенных в стеке с помощью alloca ().
бесстыдный штекер: Я писал об этом в блоге здесь.
интересная статья, но я не уверен, что понимаю преимущество этого перед boost::array. Можете ли вы немного подробнее рассказать об этом?
boost :: array требует, чтобы размер массива был константой времени компиляции. У этого нет такого ограничения.
@Ferruccio Это довольно круто, я заметил, что ваш макрос немного небезопасен, а именно размер может быть выражением. Если, например, передается x + 1, вы должны расширить его до sizeof (type) * x + 1, что было бы неверно. Вам нужно заключить макрос в скобки, чтобы сделать его более безопасным.
Использование с alloca кажется мне опасным, если возникает исключение, поскольку вам нужно вызывать деструкторы для всех ваших объектов.
Мы используем его с настраиваемыми пулами памяти. Просто набросок:
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, ...);
Теперь вы можете кластеризовать объекты вместе на одной арене памяти, выбрать распределитель, который очень быстрый, но не освобождает, использовать отображение памяти и любую другую семантику, которую вы хотите наложить, выбрав пул и передав его в качестве аргумента для размещения объекта. новый оператор.
Ага. Мы довольно сообразительны в этом вопросе, но это не по теме этого вопроса.
@jdkoftinoff есть ли у вас ссылка на реальный образец кода? кажется мне довольно интересным!
@DonWakefield Как вы справляетесь с выравниванием в этом пуле? Разве вы не должны передать выравнивание в качестве аргумента allocate()?
@MikhailVasilyev, в реальной реализации вы, конечно, справитесь с этим. Только пример кода.
что, если место размещения является недопустимым адресом, скажем 0x0?
Я использовал его для создания объектов на основе памяти, содержащей сообщения, полученные из сети.
Как правило, новое размещение используется для того, чтобы избавиться от затрат на размещение «нормального нового».
Другой сценарий, в котором я его использовал, - это место, где я хотел получить доступ к указатель для объекта, который еще должен был быть построен, для реализации синглтона для каждого документа.
Я использовал его в программировании в реальном времени. Обычно мы не хотим выполнить любое динамическое распределение (или освобождение) после запуска системы, потому что нет гарантии, сколько времени это займет.
Что я могу сделать, так это предварительно выделить большой кусок памяти (достаточно большой, чтобы вместить любое количество того, что может потребоваться классу). Затем, когда я выясню во время выполнения, как создавать объекты, новое размещение можно использовать для создания объектов именно там, где я хочу. Одна ситуация, в которой я использовал его, заключалась в том, чтобы помочь создать разнородный кольцевой буфер.
Это, конечно, не для слабонервных, но именно поэтому они делают его синтаксис довольно корявым.
Привет, TED, не могли бы вы рассказать больше об имеющемся у вас решении. Я думаю о заранее выделенном решении, но пока не добился большого прогресса. Заранее спасибо!
Что ж, реальный гетерогенный код кольцевого буфера было действительно сложной задачей, чтобы разобраться в ней. Новый palcement выглядит немного мрачновато, но по сравнению с ним это не было проблемой.
Единственное место, с которым я столкнулся, - это контейнеры, которые выделяют непрерывный буфер, а затем заполняют его объектами по мере необходимости. Как уже упоминалось, std :: vector может сделать это, и я знаю, что некоторые версии MFC CArray и / или CList сделали это (потому что именно там я впервые столкнулся с этим). Метод перераспределения буфера - очень полезная оптимизация, и размещение new в значительной степени является единственным способом создания объектов в этом сценарии. Он также иногда используется для создания объектов в блоках памяти, выделенных вне вашего прямого кода.
Я использовал его в аналогичном качестве, хотя это случается нечасто. Однако это полезный инструмент для набора инструментов C++.
Я видел, как он использовался как небольшой взлом производительности для указателя "динамического типа" (в разделе «Под капотом»):
But here is the tricky trick I used to get fast performance for small types: if the value being held can fit inside of a void*, I don't actually bother allocating a new object, I force it into the pointer itself using placement new.
Что означает если удерживаемое значение может уместиться в пустоте *? Всегда можно присвоить void * любой тип указателя. Не могли бы вы показать нам какой-нибудь пример?
@ anurag86: На моей 64-битной машине void* занимает 8 байт. Немного глупо указывать восьмибайтовый void* на однобайтовый bool. Но вполне возможно наложить bool на void*, как на union { bool b; void* v }. Вам нужен способ узнать, что то, что вы назвали void*, на самом деле является bool (или short, или float и т. д.). В статье, на которую я ссылаюсь, описывается, как это сделать. И, чтобы ответить на исходный вопрос, размещение new - это функция, используемая для создания bool (или другого типа), где ожидается void* (приведения используются для последующего получения / изменения значения).
@ anurag86: Это не одно и то же, но вас могут заинтересовать тегированные указатели (en.wikipedia.org/wiki/Tagged_pointer).
Механизмы сценариев могут использовать его в собственном интерфейсе для выделения собственных объектов из сценариев. См. Примеры в Angelscript (www.angelcode.com/angelscript).
Компьютерщик: БИНГО! Вы получили это полностью - это именно то, для чего он идеально подходит. Во многих встроенных средах внешние ограничения и / или общий сценарий использования вынуждают программиста отделить выделение объекта от его инициализации. В совокупности C++ называет это «созданием экземпляра»; но всякий раз, когда действие конструктора должно быть вызвано явно БЕЗ динамического или автоматического выделения, размещение new - это способ сделать это. Это также идеальный способ найти глобальный объект C++, который привязан к адресу аппаратного компонента (ввод-вывод с отображением памяти) или для любого статического объекта, который по какой-либо причине должен находиться по фиксированному адресу.
См. Файл 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;
Это может быть удобно при использовании разделяемой памяти, среди прочего ... Например: http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions.conditions_anonymous_example
Это также полезно, когда вы хотите повторно инициализировать глобальные или статически распределенные структуры.
Старый способ 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.
}
Разве вам не нужно было бы произвести соответствующее уничтожение перед повторной инициализацией таким образом?
[Отредактировано по орфографии] Обычно - да. Но иногда, когда вы знаете, что класс не выделяет память или другие ресурсы (или вы освободили их извне - например, когда вы используете пулы памяти), вы можете использовать этот метод. Это гарантирует, что указатели v-таблицы не будут перезаписаны. - nimrodm 16 часов назад
Даже в C использование установки всех битов в 0 гарантируется только для создания представления 0 для целочисленных типов, а не для других типов (нулевой указатель может иметь ненулевое представление).
@curiousguy - для примитивных типов вы правы (это сделает программу предсказуемой, что является преимуществом при отладке). Однако для типов данных C++ будет запущен конструктор (на месте) и они будут правильно инициализированы.
Размещение new также очень полезно при сериализации (например, с boost :: serialization). За 10 лет использования 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)
Erm, типы данных, не относящиеся к POD, может должны быть объявлены в объединении, пока вы предоставляете объединение ctor - и эй - этот ctor вероятно использовал бы размещение new для инициализации своего подкласса, отличного от POD. Ссылка: stackoverflow.com/a/33289972/2757035 Новое изобретение этого колеса с использованием произвольно большого массива байтов - впечатляющий акробатический трюк, но он кажется совершенно ненужным. Итак, что я пропустил? :)
Вы пропустили все версии C++ до C++ 11, которые во многих случаях все еще нуждаются в поддержке. :)
Я думаю, что это не было подчеркнуто каким-либо ответом, но еще один хороший пример и использование новое размещение - уменьшить фрагментацию памяти (за счет использования пулов памяти). Это особенно полезно во встроенных системах и системах высокой доступности. В последнем случае это особенно важно, потому что для системы, которая должна работать 24/365 дней, очень важно не иметь фрагментации. Эта проблема не имеет ничего общего с утечкой памяти.
Даже когда используется очень хорошая реализация malloc (или аналогичная функция управления памятью), очень трудно иметь дело с фрагментацией в течение длительного времени. В какой-то момент, если вы не будете грамотно управлять вызовами резервирования / освобождения памяти, вы можете получить много небольшие зазоры, которые трудно повторно использовать (назначить для новых резервирований). Итак, одним из решений, которые используются в этом случае, является использование пула памяти для предварительного выделения памяти для объектов приложения. Впоследствии каждый раз, когда вам нужна память для какого-либо объекта, вы просто используете новое размещение для создания нового объекта в уже зарезервированной памяти.
Таким образом, как только ваше приложение запустится, у вас уже будет зарезервирована вся необходимая память. Все новое резервирование / освобождение памяти идет в выделенные пулы (у вас может быть несколько пулов, по одному для каждого класса объектов). В этом случае не происходит фрагментации памяти, так как не будет пропусков, и ваша система может работать очень долгие периоды (годы), не страдая от фрагментации.
Я видел это на практике специально для VxWorks RTOS, поскольку ее система распределения памяти по умолчанию сильно страдает от фрагментации. Таким образом, выделение памяти стандартным методом new / malloc в проекте было в основном запрещено. Все резервирования памяти должны поступать в выделенный пул памяти.
На самом деле это отчасти требуется для реализации любой структуры данных, которая выделяет больше памяти, чем минимально требуется для количества вставленных элементов (то есть что-либо, кроме связанной структуры, которая выделяет по одному узлу за раз).
Возьмите контейнеры типа unordered_map, vector или deque. Все они выделяют больше памяти, чем минимально требуется для элементов, которые вы вставили до сих пор, чтобы не требовать выделения кучи для каждой отдельной вставки. В качестве простейшего примера возьмем vector.
Когда вы это сделаете:
vector<Foo> vec;
// Allocate memory for a thousand Foos:
vec.reserve(1000);
... это на самом деле не создает тысячу Фу. Он просто выделяет / резервирует для них память. Если бы vector не использовал новое размещение здесь, он бы создавал Foos по умолчанию повсюду, а также вынужден был бы вызывать их деструкторы даже для элементов, которые вы никогда даже не вставляли.
Распределение! = Строительство, Освобождение! = Разрушение
Вообще говоря, для реализации многих структур данных, подобных приведенным выше, вы не можете рассматривать выделение памяти и создание элементов как одну неделимую вещь, и вы также не можете рассматривать освобождение памяти и уничтожение элементов как одну неделимую вещь.
Между этими идеями должно быть разделение, чтобы избежать излишнего вызова конструкторов и деструкторов слева и справа, и поэтому стандартная библиотека разделяет идею std::allocator (которая не создает и не уничтожает элементы при выделении / освобождении памяти *). из контейнеров, которые его используют, которые создают элементы вручную, используя размещение new, и вручную уничтожают элементы, используя явные вызовы деструкторов.
- I hate the design of
std::allocatorbut that's a different subject I'll avoid ranting about. :-D
В любом случае, я часто использую его, поскольку я написал ряд универсальных контейнеров C++, совместимых со стандартами, которые нельзя было построить на основе существующих. Среди них - небольшая векторная реализация, которую я построил пару десятилетий назад, чтобы избежать выделения кучи в общих случаях, и дерево-дерево с эффективным использованием памяти (не выделяет по одному узлу за раз). В обоих случаях я не мог реализовать их, используя существующие контейнеры, и поэтому мне пришлось использовать placement new, чтобы избежать излишнего вызова конструкторов и деструкторов для ненужных вещей слева и справа.
Естественно, если вы когда-либо работали с настраиваемыми распределителями для индивидуального распределения объектов, например, со свободным списком, вы также обычно хотели бы использовать placement new, например этот (базовый пример, который не беспокоит безопасность исключений или RAII):
Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);
Вот убийственное использование конструктора C++ на месте: выравнивание по строке кеша, а также другие полномочия двух границ. Вот мой сверхбыстрый алгоритм выравнивания указателя до любой степени двух границ с 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 ();
Разве это не вызовет у вас улыбку (:-). Я ♥♥♥ C++ 1x
У меня тоже есть идея. В C++ есть принцип нулевых накладных расходов. Но исключения не следуют этому принципу, поэтому иногда они отключаются переключателем компилятора.
Давайте посмотрим на этот пример:
#include <new>
#include <cstdio>
#include <cstdlib>
int main() {
struct A {
A() {
printf("A()\n");
}
~A() {
printf("~A()\n");
}
char data[1000000000000000000] = {}; // some very big number
};
try {
A *result = new A();
printf("new passed: %p\n", result);
delete result;
} catch (std::bad_alloc) {
printf("new failed\n");
}
}
Мы размещаем здесь большую структуру, проверяем, успешно ли выделено, и удаляем ее.
Но если у нас отключены исключения, мы не сможем использовать блок try и не сможем обработать ошибку new [].
Итак, как мы можем это сделать? Вот как:
#include <new>
#include <cstdio>
#include <cstdlib>
int main() {
struct A {
A() {
printf("A()\n");
}
~A() {
printf("~A()\n");
}
char data[1000000000000000000] = {}; // some very big number
};
void *buf = malloc(sizeof(A));
if (buf != nullptr) {
A *result = new(buf) A();
printf("new passed: %p\n", result);
result->~A();
free(result);
} else {
printf("new failed\n");
}
}
UPD@Бесполезный написал комментарий, который открыл мне, что существует новый (без стрелы), который следует использовать в этом случае, но не метод, который я написал ранее. Пожалуйста, не используйте код, который я написал ранее. Извиняюсь.
На самом деле @ бесполезно, ты здесь. Я даже не знал про nothrow. Как я вижу, мой ответ можно выбросить на помойку. Как вы думаете, я должен убрать ответ?
Он по-прежнему правильный, поэтому я не вижу необходимости его удалять. Нет ничего плохого в том, чтобы оставить его вместе с комментарием.
@Useless Внес правку. Спасибо.
У меня есть еще одна идея (она актуальна для C++ 11).
Давайте посмотрим на следующий пример:
#include <cstddef>
#include <cstdio>
int main() {
struct alignas(0x1000) A {
char data[0x1000];
};
printf("max_align_t: %zu\n", alignof(max_align_t));
A a;
printf("a: %p\n", &a);
A *ptr = new A;
printf("ptr: %p\n", ptr);
delete ptr;
}
В стандарте C++ 11 GCC дает следующий выход:
max_align_t: 16
a: 0x7ffd45e6f000
ptr: 0x1fe3ec0
ptr не выровнен должным образом.
В стандарте C++ 17 и выше GCC дает следующий выход:
max_align_t: 16
a: 0x7ffc924f6000
ptr: 0x9f6000
ptr выровнен правильно.
Насколько мне известно, стандарт C++ не поддерживал новое выравнивание до появления C++ 17, и если ваша структура имеет выравнивание больше, чем max_align_t, у вас могут возникнуть проблемы.
Чтобы обойти эту проблему в C++ 11, вы можете использовать aligned_alloc.
#include <cstddef>
#include <cstdlib>
#include <cstdio>
#include <new>
int main() {
struct alignas(0x1000) A {
char data[0x1000];
};
printf("max_align_t: %zu\n", alignof(max_align_t));
A a;
printf("a: %p\n", &a);
void *buf = aligned_alloc(alignof(A), sizeof(A));
if (buf == nullptr) {
printf("aligned_alloc() failed\n");
exit(1);
}
A *ptr = new(buf) A();
printf("ptr: %p\n", ptr);
ptr->~A();
free(ptr);
}
ptr в данном случае является выровнен.
max_align_t: 16
a: 0x7ffe56b57000
ptr: 0x2416000
Это как раз та информация, которую я искал, для вызова конструкторов объектов при увеличении выделенных пулов памяти. (Надеемся, что эти ключевые слова помогут кому-то найти в будущем).