Placement new c что это
Перейти к содержимому

Placement new c что это

  • автор:

Placement new c что это

Как известно есть так называемый оператор placement new. Он реально не выделяет память, а только вызывает конструктор объекта. Также существует парный ему placement delete. Вот их определения из STL ():

inline void *__cdecl operator new(size_t, void *_Where) _THROW0() < // construct array with placement at _Where return (_Where); >inline void __cdecl operator delete(void *, void *) _THROW0() < // delete if placement new fails >

Вот пример из последнего MSDN:

// new_op_new.cpp // compile with: /EHsc #include #include using namespace std; class MyClass < public: MyClass( ) < cout ; ~MyClass( ) < imember = 0; cout ; int imember; >; int main( ) < // The first form of new delete MyClass* fPtr = new MyClass( ); delete fPtr; // The second form of new delete char x[sizeof( MyClass )]; MyClass* fPtr2 = new( &x[0] ) MyClass( ); delete(&x[0], fPtr2 ); cout 

Проблема в том, что в VisualC++ placement delete не вызывается. При просмотре ассемблерного кода, видно, что вместо operator delete(void*, void*) вызывается `scalar deleting destructor’, который после вызова деструктора вызывает глобальный operator delete(void*). Естественно при попытке освобождения памяти возникает ошибка.

Эта проблема существовала в VC++ 6.0, осталась она и в VC++ 7.0 (aka .NET).

Borland C++ и GNU C++ с этим примером справляются, хотя и не так изящно. Там не определяется placement delete. Просто компилятор знает, что при использовании такого синтаксиса delete вызывать не нужно и просто генерирует вызов деструктора. Конечно, я тоже могу просто вызвать деструктор, но это как-то некрасиво.

P.S. Кто нибудь знает, где можно взять стандарт по C++ (ISO/IEC 14882-1998) бесплатно. А то буржуи просто так не отдают.

Зачем нужна операция new с размещением?

Чем отличается инициализация p1 от p2 ? Если отличий нет, то зачем тогда нужна операция new с размещением?

Отслеживать
задан 4 мар 2021 в 19:41
Johnny Sins Johnny Sins
107 5 5 бронзовых знаков

Прошу прощения, а откуда у вас берется вообще термин «размещение»? просто общепринятым является «разыменование»

4 мар 2021 в 19:48
@S.H. placement new размещает создаваемый объект в указанном буфере.
4 мар 2021 в 19:49

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

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

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

Кроме собственно вызова конструктора, у placement-new есть второе, более туманное назначение. Он создает объект, начинает его lifetime (жизнь).

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

Попытка получить доступ к неживому объекту вызывает неопределенное поведение, просто потому что в стандарте так написано. Хотя, на практике, если ваш тип — не класс, или класс с тривиальным конструктором по умолчанию, часто все будет работать как надо.

Placement-new возвращает указатель на свежесозданный объект. В вашем примере, p1 указывает на свежесозданный int , а p2 не указывает, хотя численные значения указателей одинаковые.

Почему «не указывает»? Потому что в стандарте так написано. Попытка что-то прочитать или записать в *p2 вызовет неопределенное поведение. Однако, *std::launder(p2) будет работать.

Зачем нужно такое неопределенное поведение? Вероятно, чтобы разрешить компилятору выполнять более жесткие оптимизации, связанные со strict aliasing.

[C++] Placement new из обнуленной памяти

Цель, чтобы все поля объекта, которые не имеют собственного конструктора/не инициализируются внутри конструктора были обнулены по умолчанию.

Например:
class A
<
public:
int a,b;
>;
A *a = new A;
//a->a == 0
//a->b == 0

void * p = cleared_malloc(sizeof(A));
A * a = new (p) A;
Есть ли гарантия, что тут a->a == 0 и a->b == 0 или это UB?

cleared_malloc — выделение и обнуление выделенной памяти

Из опыта: msvc, gcc отрабатывают нормально. На gcc была проблема, когда компилятор посчитал себя шибко умными и в целях оптимизации выкинул memset перед placement new, но это было решено изменением настройки компилятора.

  • return []()<>;
  • Участник

#1
17:58, 21 апр 2020

Это UB и gcc совершенно правильно сделал

Правило 52: Если вы написали оператор new с размещением, напишите и соответствующий оператор delete

Операторы new и delete с размещением встречаются в C++ не слишком часто, поэтому в том, что вы с ними не знакомы, нет ничего страшного. Вспомните (правила 16 и 17), что когда вы пишете такое выражение new:

Widget *pw = new Widget;

то вызываются две функции: оператор new, чтобы выделить память, и конструктор Widget по умолчанию.

Предположим, что первый вызов завершился успешно, а второй возбудил исключение. В этом случае необходимо отменить выделение памяти, выполненное на шаге 1. В противном случае мы получим утечку памяти. Пользовательский код не может освободить память, потому что конструктор Widget возбудил исключение и pw ничего так и не было присвоено. Следовательно, пользователь так и не получил указатель на память, которая должна быть освобождена. Поэтому ответственность за отмену шага 1 возлагается на систему времени исполнения C++.

Исполняющая система рада бы вызвать оператор delete, соответствующий использованному на шаге 1 оператору new, но сделать это может лишь тогда, когда знает, какой именно вариант оператора delete – а их много – нужно вызвать. Это не проблема, если вы пользуетесь формами new и delete с обычными сигнатурами, потому что обычный оператор new:

void *operator new(std::size_t size) throw(std::bad_alloc);

соответствует обычному оператору delete:

void operator delete(void *rawMemory) throw(); // обычная сигнатура

// в глобальной области

void operator delete(void *rawMemory, // наиболее распространенная

std::size_t size) throw(); // сигнатура в области

Если вы пользуетесь только обычными формами new и delete, то исполняющая система легко найдет тот вариант delete, который знает, как отменить действие, выполненное оператором new. Проблема поиска правильного варианта delete возникает тогда, когда вы объявляете необычные формы оператора new – такие, которые принимают дополнительные параметры.

Например, предположим, что вы написали оператор new уровня класса, который требует задания потока ofstream, куда должна выводиться отладочная информация о выделении памяти, и вместе с ним написали также обычный оператор delete уровня класса:

static void *operator new(std:size_t size, // необычная

std::ostream& logStream) // форма new

static void operator delete(void *pMemory, // обычная

std:size_t size) throw(); // форма delete

Такое решение наверняка приведет к ошибкам, но чтобы понять, почему это так, придется познакомиться с некоторыми терминами.

Функция operator new, принимающая дополнительные параметры (помимо обязательного аргумента size_t), называется оператором new с размещением или размещающим оператором new (placement new). Приведенный выше оператор new как раз и является таковым. Особенно полезным бывает размещающий оператор new, для которого вторым аргументом служит указатель на область памяти, где объект должен быть сконструирован. Этот оператор new выглядит так:

void *operator new(std::size_t, void *pMemory) throw(); // “размещающий new”

Эта версия new является частью стандартной библиотеки C++, и вы получаете к ней доступ, включая в исходный текст директиву #include . Кстати говоря, такой оператор new используется в реализации класса vector для создания объектов в выделенной для вектора памяти. Это также первоначальная версия оператора new с размещением; именно она и получила название «placement new». Таким образом, сам термин «размещающий new» перегружен. Обычно, когда говорят о размещающем new, имеют в виду эту конкретную функцию: оператор new, принимающий дополнительный аргумент типа void*. Реже так говорят о любой другой версии new, принимающей дополнительные аргументы. Обычно контекст исключает противоречивые толкования, но важно понимать, что общий термин «размещающий new» означает любую версию new, принимающую дополнительные аргументы, поскольку выражение «размещающий delete» или «delete с размещением» (которое мы сейчас обсудим) происходит от него.

Но вернемся к объявлению класса Widget, которое я не одобрил. Проблема в том, что этот класс открывает возможность утечки памяти. Рассмотрим следующий пользовательский код, который протоколирует информацию о выделении памяти в поток cerr при динамическом создании объектов Widget:

Widget *pw = new (std::cerr) Widget; // вызвать оператор new, передав cerr

// в качестве параметра типа ofstream;

// это ведет к утечке памяти в случае,

// когда конструктор Widget возбуждает

Если выделение памяти прошло успешно, но конструктор Widget возбуждает исключение, то исполняющая система отвечает за освобождение той памяти, которую успел выделить оператор new. Исполняющая система понятия не имеет, как работает вызванная версия оператора new, поэтому не может отменить результат операции самостоятельно. Вместо этого исполняющая система ищет версию оператора delete, которая принимает то же количество аргументов того же типа, что и new, и если находит его, то вызывает. В данном случае оператор new принимает дополнительный аргумент типа ostream&, поэтому соответствующий оператор delete должен иметь следующую сигнатуру:

void operator delete(void *, std::ostream&) throw();

По аналогии с размещающими версиями new версии оператора delete, которые принимают дополнительные параметры, называются размещающими delete. Но в классе Widget не объявлена размещающая версия оператора delete, поэтому исполняющая система не знает, как отменить то, что сделал размещающий new. В результате она не делает ничего. В этом примере никакой оператор delete не вызывается, если конструктор Widget возбуждает исключение!

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

static void *operator new(std:size_t size, std::ostream& logStream)

static void operator delete(void *pMemory) throw();

static void operator delete(void *pMemory, std::ostream& logStream)

С этим изменением, если конструктор Widget возбудит исключение в предложении

Widget *pw = new (std::cerr) Widget; // как раньше, но теперь никаких

то автоматически будет вызван соответственный размещающий оператор delete, так что Widget гарантирует, что никаких утечек памяти по этой причине не будет.

Посмотрим, что произойдет, если никаких исключений нет (как обычно и бывает), а в пользовательском коде присутствует явный вызов delete:

delete pw; // вызов обычного оператора delete

Как сказано в комментарии, здесь вызывается обычный оператор delete, а не размещающая версия. Размещающий delete вызывается, только если возбуждает исключение конструктор, следующий за вызовом размещающего new. Если delete применяется к указателю (в примере выше – pw), то версия delete с размещением никогда не будет вызвана.

Это значит, что для предотвращения всех утечек памяти, ассоциированных с размещающей версией new, вы должны также предоставить и обычный оператор delete (на случай, если в конструкторе не возникнет исключений), и размещающую версию с теми же дополнительными аргументами, что и у размещающего new (если таковой имеется). Поступайте так, и вы никогда не потеряете сон из-за неуловимых утечек памяти. Ну, по крайней мере, из-за утечек памяти по этой причине.

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

static void *operator new(std::size_t size, // скрывает обычные

std::ostream& logStream) // глобальные формы

Base *pb = new Base; // ошибка! Обычная форма

// оператора new скрыта

Base *pb = new (std::cerr)Base; // правильно, вызывается

// размещающий new из Base

Аналогично оператор new в производных классах скрывает и глобальную, и унаследованную версии оператора new:

class Derived: public Base

static void *operator new(std::size_t size) // переопределяет

throw(std::bad_alloc); // обычную форму new

Derived *pd = new (std::cerr)Derived; // ошибка! заменяющая

// форма теперь скрыта

Derived *pd = new Derived; // правильно, вызывается

// оператор new из Derived

В правиле 33 достаточно подробно рассмотрен этот вид сокрытия имен в классе, но при написании функций распределения памяти нужно помнить, что по умолчанию C++ представляет следующие формы оператора new в глобальной области видимости:

void operator new(std::size_t) throw(bad_alloc); // обычный new

void operator new(std::size_t, void*) throw(bad_alloc); // размещающий new

void operator new(std::size_t, // new, не возбуждающий

const std::nothrow_t&) throw(); // исключений –

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

Самый простой способ – создать базовый класс, содержащий все нормальные формы new и delete:

static void *operator new(std::size_t size) throw(bad_alloc)

static void operator delete(void *pMemory) throw()

static void *operator new(std::size_t size, void *ptr) throw(bad_alloc)

static void operator delete(void *pMemory, void *ptr) throw()

// не возбуждающие исключений new/delete

static void *operator new(std::size_t, const std::nothrow_t& nt) throw()

static void operator delete(void *pMemory, const std::nothrow_t&) throw()

Пользователи, которые хотят пополнить свой арсенал специальными формами new, применяют наследование и using-объявления (см. правило 33), чтобы получить доступ к стандартным формам:

class Widget: public StandardNewDeleteForms < // наследование

public: // стандартных форм

using StandardNewDeleteForms::operator new; // сделать эти формы

using StandardNewDeleteForms::operator delete; // видимыми

static void *operator new(std::size_t size, // добавляется

std::ostream& logStream) // специальный

throw(bad_alloc); // размещающий new

static void operator delete(void *pMemory, // добавляется

std::ostream& logStream) // соответствующий

throw(); // размещающий delete

Что следует помнить

• Когда вы пишете размещающую версию оператора new, убедитесь, что не забыли о соответственном размещающем операторе delete. Если его не будет, то в вашей программе могут возникать тонкие, трудноуловимые утечки памяти.

• Объявляя размещающие версии new и delete, позаботьтесь о том, чтобы нечаянно не скрыть нормальных версий этих функций.

Данный текст является ознакомительным фрагментом.

Продолжение на ЛитРес

Читайте также

5.3.2 Оператор &

5.3.2 Оператор & Оператор используется для того, чтобы организовать исполнение команд в фоновом режиме. Если поставить значок после команды, то оболочка вернет управление пользователю сразу после запуска команды, не дожидаясь, пока выполнение команды завершится.

Правило 16: Используйте одинаковые формы new и delete

Правило 16: Используйте одинаковые формы new и delete Что неправильно в следующем фрагменте?std::string *stringArray = new std::string[100];. delete stringArray;На первый взгляд, все в полном порядке – использованию new соответствует применение delete, но кое-что здесь совершенно неверно. Поведение программы

Правило 50: Когда имеет смысл заменять new и delete

Правило 50: Когда имеет смысл заменять new и delete Вернемся к основам. Прежде всего зачем кому-то может понадобиться подменять предлагаемые компилятором версии operator new и operator delete? Существуют, по крайней мере, три распространенные причины.• Чтобы обнаруживать ошибки

Правило 51: Придерживайтесь принятых соглашений при написании new и delete

Правило 51: Придерживайтесь принятых соглашений при написании new и delete В правиле 50 объясняется, зачем могут понадобиться собственные версии операторов new и delete, но ничего не говорится о соглашениях, которых следует придерживаться при их написании. Следовать этим

Совет 7. При использовании контейнеров указателей, для которых вызывался оператор new, не забудьте вызвать delete для указателей перед уничтожением контейнера

Совет 7. При использовании контейнеров указателей, для которых вызывался оператор new, не забудьте вызвать delete для указателей перед уничтожением контейнера Контейнеры STL отличаются умом и сообразительностью. Они поддерживают итераторы для перебора как в прямом, так и в

1. Оператор Select – базовый оператор языка структурированных запросов

1. Оператор Select – базовый оператор языка структурированных запросов Центральное место в языке структурированных запросов SQL занимает оператор Select, с помощью которого реализуется самая востребованная операция при работе с базами данных – запросы.Оператор Select

15.8.2. Оператор размещения new() и оператор delete()

15.8.2. Оператор размещения new() и оператор delete() Оператор-член new() может быть перегружен при условии, что все объявления имеют разные списки параметров. Первый параметр должен иметь тип size_t:class Screen ;Остальные параметры

Оператор DELETE

Оператор DELETE Запрос DELETE используется для удаления целых строк таблицы. SQL не дает возможности одному оператору DELETE удалять строки более чем из одной таблицы. Запрос DELETE, который изменяет только одну текущую строку курсора, называется позиционированным удалением.

9.5 Оператор Do

9.5 Оператор Do Оператор do имеет видdo оператор while ( выражение ) ;Выполнение подоператора повторяется до тех пор, пока значение остается не нулем. Проверка выполняется после каждго выполнения оператора. Выражение обрабатывается как в уловном операторе

9.6 Оператор For

9.6 Оператор For Оператор for имеет видfor (оператор_1 выражение_1 opt; выражение_2 opt) оператор_2Этот оператор эквивалентен следующему:оператор_1 while ( выражение_1 ) (* оператор_2 выражение_2 ; *)за исключением того, что continue в операторе_2 будет выполнять выражение_2 перед выполнением

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *