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

char j[999];
int* p1 =new (j) int;
int* p2 =(int*) j;

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


Ответы (3 шт):

Автор решения: user7860670

При new c размещением объект создается, а при касте - нет. Во втором случае разыменование указателя будет являться неопределенным поведением. А в первом - нет, указатель p1 указывает на только что созданный объект int. Для объекта типа int разница выходит чисто формальная, а вот для любого объекта с нетривиальным конструктором, например std::string, без размещения с new будет гарантированное фиаско.

→ Ссылка
Автор решения: AlexGlebe

Оператор new Type выделяет память и вызывает конструктор объекта типа Type.

Оператор new с размещением только вызывает конструктор в указанном месте памяти.

Его используют если свободная память уже есть, а нужно только про инициализировать объект.

class Type{
..
} ;
..
// выделяем память для 10-и объектов типа Type
Type * p = malloc(sizeof(Type)*10);
// вызываем конструктор 5-го элемента
new(&p[5])Type();
// вызываем его деструктор 
p[5] . ~ Type();
// освобождает память от десяти объектов
free(p);

Команда delete - аналогично вызывает деструктор объекта и освобождает память.

Прямой вызов деструктора делает только свою работу.


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

// g++ -Wall -Wextra -Wpedantic -std=c++11 lazyc.cpp -o lazyc
# include <new>
# include <iostream>
class C{
public:  
  C(){std::cout<<"C()"<<std::endl;}
  ~C(){std::cout<<"~C()"<<std::endl;}
};

class LazyC{
public:
  
  LazyC(){}
  
  void newC(){
    if(live){
      c . ~ C ( ) ;
      new ( & c ) C ( ) ; }
    else{
      new ( & c ) C ( ) ;
      live = true ; }}
      
  void delC(){
    if (live){
      c . ~ C ( ) ;
      live = false ; }}
      
  ~LazyC(){
    if (live)
      c . ~ C ( ) ; }
      
private :
    
  union{
    C c;
  };
  
  bool live { false };
  
};

int main(){
  LazyC l;
  std::cout<<"main:sleep"<<std::endl;
  l.newC();
  std::cout<<"main:exist"<<std::endl;
  l.delC();
  std::cout<<"main:closed"<<std::endl;
}

проверка

> ./lazyc
main:sleep
C()
main:exist
~C()
main:closed
→ Ссылка
Автор решения: HolyBlackCat

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

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

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

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

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

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

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

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

→ Ссылка