Почему конструкторы не могут быть явно вызваны, пока деструкторы могут?

В следующем коде на С++ я могу явно вызвать деструктор, но не конструктор. Почему это? Не будет ли явным ctor вызывать более выразительный и унифицированный с dtor-кодом?

class X { };

int main() {
  X* x = (X*)::operator new(sizeof(X));
  new (x) X;  // option #1: OK
  x->X();     // option #2: ERROR

  x->~X();
  ::operator delete(x);
}

Ответ 1

Так как перед запуском конструктора на этом адресе нет объекта типа X. Таким образом, разыменование X как типа X или доступ к его членам/методам будет Undefined Behavior.

Таким образом, основное различие между x->X(); (гипотетический синтаксис) и x->~X() заключается в том, что во втором случае у вас есть объект, по которому вы можете вызвать (специальный) элемент, такой как деструктор, в то время как в первом случае, еще нет объекта, на котором вы можете вызывать методы (даже специальный метод - конструктор).

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

Ответ 2

Это вариация проблемы с курицей и яйцом.

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

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

Единственное исключение - это когда вы выделили память для объекта, но еще не инициализировали экземпляр (т.е. память для экземпляра есть, но она не была инициализирована, чтобы стать фактическим экземпляром). Следовательно, вам нужно вызвать конструктор. Это ситуация, когда размещение new, синтаксис, отображаемый в комментарии "вариант 1", полезен. Однако этот вызов не является членом, который вы выполняете в экземпляре, потому что экземпляр недоступен до совершения этого вызова.

Ответ 3

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

Новый вызов() может быть наложен параметрами; конструктор размещения принимает либо void*, либо указатель на тип. Функция new() всегда принимает параметр size_t, размер sizeof(); это обычно используется только глобальной новой функцией

Конструктор размещения и объяснение деструктора используются при записи пулов памяти.

Например (из памяти!)

class MyClass
{
    public:
       inline new(size_t size, MyClass *ptr) { return ptr; };
};

Используется как

{
    MyClass *x = ...;
    MyClass *y = new (x) MyClass(construct parameters);
    x->~MyClass();
}

Отредактировано, чтобы исправить ошибку, указанную @Ben-Voigt