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

Имея класс 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++?. Но мой случай другой, так как в моем коде проблема потенциальных вызовов деструктора из-за исключений не распространяется (или, по крайней мере, я ее там не вижу).

Это сокровенное X() создает временный объект, который необходимо уничтожить.

Pete Becker 15.07.2024 15:11

@PeteBecker Разве обязательное удаление копирования не предотвращает этого?

HolyBlackCat 15.07.2024 15:15

@PeteBecker Насколько я понимаю, если бы существовал временный объект, то объект нужно было бы инициализировать с помощью конструктора копирования или перемещения. Чего больше не происходит со времен C++17.

Daniel Langr 15.07.2024 15:17

MSVC компилирует его как есть. Также, если мы используем new (ptr) X;, все три компилятора его принимают. См. демо, так что, возможно, временное сокровенное X() как-то связано с проблемой.

wohlstad 15.07.2024 15:21

@HolyBlackCat Я тоже этого ожидал... но учитывая, откуда берутся компиляторы. Возможно, исключение копирования — это оптимизация, применяемая после первого прохода. MSVC, по крайней мере некоторое время назад, даже не применял (если это правильное слово) исключение копирования в отладочных сборках.

Pepijn Kramer 15.07.2024 15:21

@PepijnKramer Термин «исключение копирования» очень вводит в заблуждение в случаях «отложенной материализации». AFAIK, здесь нечего упускать.

Daniel Langr 15.07.2024 15:23

@DanielLangr -- возможно; но в сообщении об ошибке в вашей ссылке Godbolt есть стрелка, указывающая на это временно. На это жалуется компилятор. Правильно ли это - другой вопрос (комментировать который я не уполномочен, так как несколько лет назад перестал обращать внимание на разработку стандарта)

Pete Becker 15.07.2024 15:24

С сайта cppreference.com: «Деструктор возвращаемого типа должен быть доступен в точке оператора return и не удаляться, даже если ни один объект T не уничтожен.».

Jarod42 15.07.2024 15:26

@Jarod42 Да, это правило связано со значениями, возвращаемыми функциями, что отличается от моего случая. Там я могу понять причины (пожалуйста, посмотрите связанный пост в моем обновлении).

Daniel Langr 15.07.2024 15:29

Итак (а) если это незаконно, этого не должно быть. Даже если X() { } бросит, в этот момент не будет X, который можно было бы уничтожить. И (б) я бы рассмотрел гарантированные изменения стандарта. Как отмечает @Jarod42, требование к неиспользуемым деструкторам в некоторых случаях содержится в стандарте; неканонический источник, который он упоминает, говорит о гарантированном исключении возврата функции, фактический стандарт может иметь соответствующие слова для вашего случая.

Yakk - Adam Nevraumont 15.07.2024 15:31

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

François Andrieux 15.07.2024 15:40

@FrançoisAndrieux Я согласен, но это не перемещение/копирование. Вот почему, начиная с C++17, мы можем писать, например, std::atomic<int> a = 1;, что было невозможно в C++11/14.

Daniel Langr 15.07.2024 16:10

Является ли внутреннее X()материализованным временным? Для этого преобразования требуется доступный деструктор.

François Andrieux 15.07.2024 16:17

@FrançoisAndrieux В конце раздела, на который вы ссылаетесь: «Обратите внимание, что временная материализация не происходит при инициализации объекта из prvalue того же типа (путем прямой инициализации или инициализации копирования): такой объект инициализируется непосредственно из инициализатора. "

Daniel Langr 15.07.2024 16:22

@DanielLangr вы пишете: «Да, это правило связано со значениями, возвращаемыми функциями, что отличается от моего случая», но разве вызов конструктора не является функцией, которая возвращает значение типа конструктора? Элисион в сторону?

Maurycyt 15.07.2024 16:25

@Maurycyt Нет, ктор на самом деле не возвращает объект типа класса. См. Конструкторы — это функции, которые не возвращают значение.

user12002570 15.07.2024 16:26

@user12002570 user12002570 Я не согласен с этой интерпретацией. На самом деле утверждается, что ктор «не возвращает значение», но мне кажется, что на самом деле речь идет о том, что нельзя использовать return X в определении ctor. Поэтому я думаю, что это следует читать как «Конструкторы синтаксически не возвращают значение». Однако в качестве выражения обычно используется вызов конструктора. Используем ли мы вызов конструктора напрямую или функцию, которая возвращает только значение, созданное вызовом конструктора, совершенно не имеет значения, поскольку она имеет одинаковую семантику. По сути, это функция, как я написал

Maurycyt 15.07.2024 16:35

@Maurycyt Да, я уже знаю, что это функция. Я объяснил это более подробно здесь

user12002570 15.07.2024 16:37

Извините, у меня закончилось место, поэтому я написал немного двусмысленно. Я имел в виду, что это «функция, как я написал», то есть функция, которая «возвращает значение», как я уже говорил выше. Естественно, он не может «вернуть значение» в своем определении, но все равно «возвращает значение» при вызове. Отсюда мой первоначальный комментарий о том, что Дэниел ошибочно отверг правило, поскольку оно не относится к конструкторам, а только к функциям, возвращающим значение, тогда как конструктор действительно возвращает значение, даже если его нельзя явно вернуть в определении конструктора.

Maurycyt 15.07.2024 16:40

@Maurycyt Также обратите внимание, что конструкторы не могут вызываться напрямую в C++. У них нет имени. Они неявно вызываются компилятором.

user12002570 15.07.2024 16:42

@ user12002570 По моему мнению, это еще раз подтверждает мою точку зрения. Если вызов X() на самом деле не вызывает конструктор, то происходит что-то еще, похожее на вызов неявно определенной функции с тем же именем, что и у класса X. В этот момент эта функция возвращает значение, что противоречит замечанию Дэниела.

Maurycyt 15.07.2024 16:54

@Maurycyt Существует одно существенное различие между «возвратом значения» из обычных функций и конструкторов, которое здесь актуально. Обычные функции 1) создают возвращаемое значение и 2) уничтожают локальные объекты. 2) обычно может выдать ошибку, и в таких случаях ранее созданное возвращаемое значение необходимо разрушить (поэтому деструктор потенциально вызывается, как описано здесь: cplusplus.github.io/CWG/issues/2426.html) . В моем случае эта проблема не актуальна; нет необходимости потенциально уничтожать построенный объект, если что-то не удалось.

Daniel Langr 16.07.2024 11:10

Конструкторы @DanielLangr могут бросать. В этом случае им может потребоваться уничтожить некоторые объекты, в том числе полностью созданный объект, который создавался с точки зрения данных.

Maurycyt 16.07.2024 21:29

@Maurycyt Некоторые объекты, но не сам построенный объект. Если конструктор выдает ошибку, деструктор не вызывается. (За исключением делегирования конструкторов.)

Daniel Langr 18.07.2024 07:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
14
24
544
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Компиляторы ведут себя неправильно (при условии, что C++17 или более поздняя версия).

В вашем коде деструктор X не вызывается явно, он не вызывается неявно в соответствии с [class.dtor]/14 предложением 1 или предложением 3 , а также потенциально не вызывается в соответствии с [класс .dtor]/14 предложение 6.

В частности, не существует неявного вызова деструктора для уничтожения временного объекта, поскольку его не существует. Выражение new напрямую инициализирует создаваемый объект из инициализатора выражения new. Единственный аргумент в скобках в инициализаторе представляет собой prvalue типа X и согласно [dcl.init.general]/16.6.1 это означает, что целевой объект немедленно инициализируется из инициализатора аргумента prvalue. Никакого временного преобразования материализации не происходит.

Поэтому не должно иметь значения, что деструктор удален.

Что касается Clang, похоже, об этом сообщалось уже некоторое время назад, см. отчет об ошибке, но с отчетом не было никакой активности.

Другие вопросы по теме