Имея класс X
, следующая инициализация объекта:
new (ptr) X(X());
требует доступного деструктора, начиная с C++17. Почему, когда объект инициализируется конструктором по умолчанию непосредственно в хранилище, на которое указывает ptr
, не нужно использовать никаких временных объектов?
Пример кода:
struct X {
X() { }
X(const X&) = delete;
X(X&&) = delete;
~X() = delete; // or, e.g, private
};
void test(void* ptr) {
new (ptr) X(X()); // error: attempt to use a deleted function
}
Демо: https://godbolt.org/z/Khac1z8r3
ОБНОВЛЯТЬ
Это аналогичный вопрос: Почему публичный деструктор необходим для обязательного RVO в C++?. Но мой случай другой, так как в моем коде проблема потенциальных вызовов деструктора из-за исключений не распространяется (или, по крайней мере, я ее там не вижу).
@PeteBecker Разве обязательное удаление копирования не предотвращает этого?
@PeteBecker Насколько я понимаю, если бы существовал временный объект, то объект нужно было бы инициализировать с помощью конструктора копирования или перемещения. Чего больше не происходит со времен C++17.
MSVC компилирует его как есть. Также, если мы используем new (ptr) X;
, все три компилятора его принимают. См. демо, так что, возможно, временное сокровенное X()
как-то связано с проблемой.
@HolyBlackCat Я тоже этого ожидал... но учитывая, откуда берутся компиляторы. Возможно, исключение копирования — это оптимизация, применяемая после первого прохода. MSVC, по крайней мере некоторое время назад, даже не применял (если это правильное слово) исключение копирования в отладочных сборках.
@PepijnKramer Термин «исключение копирования» очень вводит в заблуждение в случаях «отложенной материализации». AFAIK, здесь нечего упускать.
@DanielLangr -- возможно; но в сообщении об ошибке в вашей ссылке Godbolt есть стрелка, указывающая на это временно. На это жалуется компилятор. Правильно ли это - другой вопрос (комментировать который я не уполномочен, так как несколько лет назад перестал обращать внимание на разработку стандарта)
С сайта cppreference.com: «Деструктор возвращаемого типа должен быть доступен в точке оператора return и не удаляться, даже если ни один объект T
не уничтожен.».
@Jarod42 Да, это правило связано со значениями, возвращаемыми функциями, что отличается от моего случая. Там я могу понять причины (пожалуйста, посмотрите связанный пост в моем обновлении).
Итак (а) если это незаконно, этого не должно быть. Даже если X() { }
бросит, в этот момент не будет X
, который можно было бы уничтожить. И (б) я бы рассмотрел гарантированные изменения стандарта. Как отмечает @Jarod42, требование к неиспользуемым деструкторам в некоторых случаях содержится в стандарте; неканонический источник, который он упоминает, говорит о гарантированном исключении возврата функции, фактический стандарт может иметь соответствующие слова для вашего случая.
Я думаю, что для обязательного перемещения по-прежнему нужны все члены, которые были бы необходимы, если бы исключение не оказалось доступным.
@FrançoisAndrieux Я согласен, но это не перемещение/копирование. Вот почему, начиная с C++17, мы можем писать, например, std::atomic<int> a = 1;
, что было невозможно в C++11/14.
Является ли внутреннее X()
материализованным временным? Для этого преобразования требуется доступный деструктор.
@FrançoisAndrieux В конце раздела, на который вы ссылаетесь: «Обратите внимание, что временная материализация не происходит при инициализации объекта из prvalue того же типа (путем прямой инициализации или инициализации копирования): такой объект инициализируется непосредственно из инициализатора. "
@DanielLangr вы пишете: «Да, это правило связано со значениями, возвращаемыми функциями, что отличается от моего случая», но разве вызов конструктора не является функцией, которая возвращает значение типа конструктора? Элисион в сторону?
@Maurycyt Нет, ктор на самом деле не возвращает объект типа класса. См. Конструкторы — это функции, которые не возвращают значение.
@user12002570 user12002570 Я не согласен с этой интерпретацией. На самом деле утверждается, что ктор «не возвращает значение», но мне кажется, что на самом деле речь идет о том, что нельзя использовать return X
в определении ctor. Поэтому я думаю, что это следует читать как «Конструкторы синтаксически не возвращают значение». Однако в качестве выражения обычно используется вызов конструктора. Используем ли мы вызов конструктора напрямую или функцию, которая возвращает только значение, созданное вызовом конструктора, совершенно не имеет значения, поскольку она имеет одинаковую семантику. По сути, это функция, как я написал
@Maurycyt Да, я уже знаю, что это функция. Я объяснил это более подробно здесь
Извините, у меня закончилось место, поэтому я написал немного двусмысленно. Я имел в виду, что это «функция, как я написал», то есть функция, которая «возвращает значение», как я уже говорил выше. Естественно, он не может «вернуть значение» в своем определении, но все равно «возвращает значение» при вызове. Отсюда мой первоначальный комментарий о том, что Дэниел ошибочно отверг правило, поскольку оно не относится к конструкторам, а только к функциям, возвращающим значение, тогда как конструктор действительно возвращает значение, даже если его нельзя явно вернуть в определении конструктора.
@Maurycyt Также обратите внимание, что конструкторы не могут вызываться напрямую в C++. У них нет имени. Они неявно вызываются компилятором.
@ user12002570 По моему мнению, это еще раз подтверждает мою точку зрения. Если вызов X()
на самом деле не вызывает конструктор, то происходит что-то еще, похожее на вызов неявно определенной функции с тем же именем, что и у класса X
. В этот момент эта функция возвращает значение, что противоречит замечанию Дэниела.
@Maurycyt Существует одно существенное различие между «возвратом значения» из обычных функций и конструкторов, которое здесь актуально. Обычные функции 1) создают возвращаемое значение и 2) уничтожают локальные объекты. 2) обычно может выдать ошибку, и в таких случаях ранее созданное возвращаемое значение необходимо разрушить (поэтому деструктор потенциально вызывается, как описано здесь: cplusplus.github.io/CWG/issues/2426.html) . В моем случае эта проблема не актуальна; нет необходимости потенциально уничтожать построенный объект, если что-то не удалось.
Конструкторы @DanielLangr могут бросать. В этом случае им может потребоваться уничтожить некоторые объекты, в том числе полностью созданный объект, который создавался с точки зрения данных.
@Maurycyt Некоторые объекты, но не сам построенный объект. Если конструктор выдает ошибку, деструктор не вызывается. (За исключением делегирования конструкторов.)
Компиляторы ведут себя неправильно (при условии, что C++17 или более поздняя версия).
В вашем коде деструктор X
не вызывается явно, он не вызывается неявно в соответствии с [class.dtor]/14 предложением 1 или предложением 3 , а также потенциально не вызывается в соответствии с [класс .dtor]/14 предложение 6.
В частности, не существует неявного вызова деструктора для уничтожения временного объекта, поскольку его не существует. Выражение new
напрямую инициализирует создаваемый объект из инициализатора выражения new
. Единственный аргумент в скобках в инициализаторе представляет собой prvalue типа X
и согласно [dcl.init.general]/16.6.1 это означает, что целевой объект немедленно инициализируется из инициализатора аргумента prvalue. Никакого временного преобразования материализации не происходит.
Поэтому не должно иметь значения, что деструктор удален.
Что касается Clang, похоже, об этом сообщалось уже некоторое время назад, см. отчет об ошибке, но с отчетом не было никакой активности.
Это сокровенное
X()
создает временный объект, который необходимо уничтожить.