Дополнительная конструкция при использовании нового размещения с классом хранилища

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

#include <cassert>
#include <iostream>

struct Object { 
  Object() { std::cout << "Creating a new object\n"; } 
  static void *operator new(size_t);
  static void operator delete(void *p);
};

static struct { 
  Object where;
  bool allocated = false;
} Storage; // 1

void *Object::operator new(size_t) { 
  assert(!Storage.allocated);
  auto p = ::new (&Storage.where) Object; // 2
  Storage.allocated = true;
  
  return p;
}

void Object::operator delete(void *p) { 
  assert(Storage.allocated);
  static_cast<Object *>(p)->~Object();
  Storage.allocated = false;
}

int main() { Object *obj = new Object; } // 3

Мой вопрос связан с количеством вызовов конструктора. Когда я запускаю указанную выше программу, я ожидаю, что конструктор будет вызываться дважды (помеченный как 1 и 2 в комментариях выше), но результат, который я получаю:

Создание нового объекта

Создание нового объекта

Создание нового объекта

Почему конструктор вызывается трижды? Я бы ожидал только вызовов конструктора статическим объектом и вызовом нового размещения. Я пробовал отслеживать код с помощью gdb, но для меня это не имеет смысла, поскольку позиция //3 равна where, инициируется третий вызов конструктора.

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


person Lorah Attkins    schedule 21.01.2020    source источник
comment
@ Jarod42 Пронумерованные комментарии - это точки останова, которые говорят мне, что вызов исходит. Один из статической инициализации, один в новом размещении; вопрос: какой третий?   -  person Lorah Attkins    schedule 21.01.2020
comment
@Someprogrammerdude Это #0 Object::Object (this=0x555555756160 <Storage>) at placement.cpp:7 #1 0x0000555555554a4f in main () at placement.cpp:31 (строка 7 — это cout, а строка 31 — единственная строка в main)   -  person Lorah Attkins    schedule 21.01.2020
comment
Разве новый оператор размещения не должен (!) вызывать новое размещение для объекта? Итак, первая — это Storage::where, вторая — в operator new, а третья — в main-функции.   -  person Simon Kraemer    schedule 21.01.2020
comment
Это неправильное понимание того, что должна делать функция operator new: она должна только выделять память, а не создавать объекты. Поэтому он не должен делать размещение-новым.   -  person Some programmer dude    schedule 21.01.2020
comment
@Someprogrammerdude Да, ты прав! В Интернете есть так много примеров, которые показывают, что конструктор вызывается помимо размещения new, что легко неправильно понять ситуацию.   -  person Lorah Attkins    schedule 21.01.2020
comment
С другой стороны (поскольку на ваш вопрос уже дан ответ), вам не нужно размещать объект статически, замените его на char buf[sizeof(Object)]; и конструктор вызывается только один раз.   -  person SPD    schedule 21.01.2020


Ответы (2)


Object *obj = new Object; делает две вещи:

  1. Выделяет память, вызывая operator new

  2. Вызывает конструктор.

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

Обратите внимание, что delete совпадает. delete obj; делает две вещи:

  1. Вызывает деструктор.

  2. Освобождает память, вызывая operator delete

Ваш operator delete также не должен вызывать деструктор, потому что тогда деструктор вызывается дважды.

person user253751    schedule 21.01.2020
comment
@Someprogrammerdude, но весь смысл шаблона в том, чтобы фактически не выделять память во время выполнения - person Lorah Attkins; 21.01.2020
comment
Вау, спасибо за дополнительное исправление ошибки, возможно, мне придется изменить выбранный ответ сейчас - person Lorah Attkins; 21.01.2020

По какой-то странной причине ваш operator new вызывает конструктор, когда он должен просто выделить память. Это означает, что вызов new завершается вызовом конструктора Object дважды. Есть один вызов в operator new и еще один вызов в main.

Вы, вероятно, хотите этого:

void *Object::operator new(size_t) { 
  assert(!Storage.allocated);
  Storage.allocated = true;
  return reinterpret_cast<void *> (&Storage.where);
}

Представьте, что конструктор принимает целочисленный параметр и строка в main выглядит так:

Object *obj = new Object(7);

Откуда operator new знать, как правильно сконструировать объект? Это не то место, где вы должны это делать!

person David Schwartz    schedule 21.01.2020