Почему это не поведение undefined для уничтожения объекта, который был перезаписан путем размещения new?

Я пытаюсь выяснить, является ли следующее поведение неопределенным. У меня такое чувство, что это не UB, но при чтении стандарта он выглядит так, как будто это UB:

#include <iostream>

struct A {
    A() { std::cout << "1"; }
    ~A() { std::cout << "2"; }
};

int main() {
    A a;
    new (&a) A;
}

Цитата из стандарта C++ 11:

basic.life¶4 говорит: «Программа может закончить время жизни любого объекта, повторно используя память, которую занимает объект»

Таким образом, после new (&a) A исходный объект A закончил свою жизнь.

class.dtor¶11.3 говорит, что «деструкторы вызываются неявно для сконструированных объектов с автоматической продолжительностью хранения ([basic.stc.auto]), когда блок, в котором создан объект, выходит ([stmt.dcl])»

Таким образом, деструктор исходного объекта A вызывается неявно при выходе из main.

class.dtor¶15 говорит: «поведение не определено, если деструктор вызывается для объекта, время жизни которого закончилось ([basic.life])».

Так что это неопределенное поведение, поскольку исходный A больше не существует (даже если новый a теперь существует в том же хранилище).

Вопрос в том, вызывается ли деструктор для исходного A или вызывается деструктор для объекта в настоящее время называется a.

Мне известно о basic.life¶7, в котором говорится, что имя a относится к новому объекту после размещения new. Но class.dtor¶11.3 явно говорит, что вызывается деструктор объект, который выходит за пределы области видимости, а не деструктор объект, на который ссылается имя, выходящее за пределы области видимости.

Я неправильно читаю стандарт или это действительно неопределенное поведение?

Обновлено: несколько человек сказали мне не делать этого. Чтобы уточнить, я определенно не планирую делать это в производственном коде! Это вопрос CppQuiz, который касается крайних случаев, а не лучших практик.

Нашел еще один ответ, который соглашается: stackoverflow.com/a/35395517/3980929 (может быть, меньше обмана, но все же интересно)

Rakete1111 03.09.2018 19:23

Вы не должны использовать новое размещение на существующем объекте, поскольку ранее созданный объект не будет уничтожен. Если при запуске вышеуказанной программы на выходе получается «112» или 1122, то это неопределенное поведение. Вам просто нужно какое-то воображение, вроде того, что произошло бы, если бы внутри A был указатель (возможно, косвенно), и вы могли бы догадаться, что это неопределенное поведение.

Phil1970 03.09.2018 19:37

@ Phil1970 [basic.life] p5 хотел бы поговорить с вами. Кроме того, там говорится, что если на выходе будет 1122, то в компиляторе есть ошибка, потому что стандарт явно запрещает это!

Rakete1111 03.09.2018 19:48

@ Rakete1111 Я не вижу в этом вопросе ничего, относящегося к этому, за исключением того, что это очень широкий вопрос, который задает практически все еще о размещении новых - только на этом основании я считаю, что это плохая цель для обмана tbh

Lightness Races in Orbit 03.09.2018 19:58

@ Rakete1111 прав насчет p5, хотя я считаю формулировку «зависит от побочных эффектов» несколько неровной и трудно поддающейся количественной оценке с научной точки зрения (поэтому лично я бы избегал этого и сначала сам вызывал dtor, за исключением объектов встроенного типа. )

Lightness Races in Orbit 03.09.2018 20:00

@Light Ах да, согласен, что это плохая цель для обманщиков. О да, это очень странная формулировка. Думаю, даже удаление этой части предложения ничего не изменит.

Rakete1111 03.09.2018 20:03

Ответ «может быть, меньше дурака» идет прямо в самую суть моего вопроса, imo. То есть: вызывается ли деструктор для 1: первого A, 2: второго A или 3: объекта, стоящего за переменной a? Ответ, на который вы ссылаетесь, утверждает, что 3 - это случай, что, на мой взгляд, имеет смысл. Однако я не думаю, что это очень ясно из [class.dtor].

knatten 03.09.2018 20:41

@knatten: Условия, при которых a обращается к новому объекту, достаточны для того, чтобы гарантировать, что «деструктор для исходного A» и «деструктор для объекта с текущим именем a» совпадают.

Ben Voigt 03.09.2018 20:50

@BenVoigt: Условий достаточно, чтобы объяснить, что происходит, если 3 истинно, и что эффект в этом случае такое же, как если бы 2 было истинным. Они не говорят, какой из них фактически true, и я пытаюсь понять это.

knatten 03.09.2018 20:59

@knatten: если условия соблюдены, оба истинны. Вызываемый деструктор является деструктором исходного объекта. И это деструктор нового объекта. (Оба деструктора одинаковы) Каким бы способом вы ни смотрели на него, разрушаемый им объект является новым объектом; оригинал больше не существует, и при попытке сослаться на него обнаруживается новый объект. Если условия не выполняются, поведение не определено.

Ben Voigt 03.09.2018 21:04

@BenVoigt Старый и новый объекты - это два разных объекта, как у них может быть один и тот же деструктор? Если бы у A был член данных, который различается между этими двумя, которые мы напечатали в деструкторе, несомненно, имело бы значение, для какого объекта мы вызываем деструктор?

knatten 03.09.2018 21:57

@knatten: деструктор - это функция (в частности, специальная функция-член). Функции-члены являются общими для всех экземпляров типа. Целевой объект - это параметр функции-члена, доступ к которой осуществляется с помощью ключевого слова this. Если вы обращаетесь к члену данных внутри деструктора, вы делаете это через this (неявно или явно, все еще есть объектный доступ к *this), и, как вы знаете, this - это указатель, который ссылается на объект, существующий в данный момент в этом месте.

Ben Voigt 03.09.2018 22:41

Даже если под «деструктором» вы имели в виду функцию-член граница, это все еще пара (дескриптор функции, дескриптор целевого объекта - может быть указателем или ссылкой), и дескриптор объекта определяет местонахождение объекта, находящегося в данный момент в этой ячейке памяти, даже если это не так. t исходный объект. В C++ указатели и ссылки привязаны к ячейкам памяти, а не к объектам.

Ben Voigt 03.09.2018 22:43

Да, я думаю, мы все согласны с тем, что происходит на практике. Вопрос в том, как стандарт объясняет это в [class.dtor], который, я думаю, мы установили, нуждается в разъяснении (согласно ответу Rakete1111).

knatten 03.09.2018 22:53

Стандарт требует, чтобы это происходило именно так, но я согласен, что это можно объяснить лучше. Причина, по которой должно существовать правило в Комментарий Петра, заключается в том, что в противном случае было бы непонятно, включены ли «существующие указатели / ссылки / имена переменных для перезаписанного объекта теперь на новый объект» вызов деструктора.

Ben Voigt 03.09.2018 22:58
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
10
15
337
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы неправильно это понимаете.

«Деструкторы вызываются неявно для сконструированных объектов»… означающих те, которые существовать и их существование дошли до полной конструкции. Оригинальный A, возможно, не полностью описан, но не отвечает этому критерию, поскольку он больше не является «сконструированным»: его вообще не существует! В конце main, как и следовало ожидать, автоматически уничтожается только новый объект / заменяющий объект.

В противном случае такая форма размещения new была бы довольно опасной и имела бы спорную ценность для языка. Тем не менее, стоит отметить, что повторное использование настоящего A таким образом немного странно и необычно, хотя бы по той причине, что это приводит именно к таким вопросам. Обычно вы помещаете new в какой-нибудь мягкий буфер (например, char[N] или какое-то выровненное хранилище), а затем сами вызываете деструктор.

Что-то похожее на ваш пример можно найти в basic.life¶8 - это UB, но только потому, что кто-то построил T поверх B; формулировка довольно ясно предполагает, что это единственная проблема с кодом.

Но вот решающий аргумент:

The properties ascribed to objects throughout this International Standard apply for a given object only during its lifetime. [..] [basic.life¶3]

Хорошая находка. Указанное вами место фактически содержит формулировку, оправдывающую конкретный случай OP: «программа должен гарантировать, что объект исходного типа занимает то же место хранения, когда имеет место неявный вызов деструктора; в противном случае поведение программы не определено». Акцент сделан мной. Обратите внимание на «исходный тип», а не «исходный объект» ;-).

Peter - Reinstate Monica 03.09.2018 19:25

@ PeterA.Schneider На самом деле это ничего не говорит нам о том, что теперь может случиться с исходным объектом, но это не обязательно :)

Lightness Races in Orbit 03.09.2018 20:00

Итак, «построенный» означает «построенный, и срок его службы еще не закончился»? Кроме того, что именно, по вашему мнению, вызывает вызов деструктора? 1: Тот факт, что имя a выходит за пределы области видимости, и теперь оно относится к новому объекту, или 2: Тот факт, что сам новый объект выходит за пределы области видимости?

knatten 03.09.2018 20:07

Я согласен, что это необычно, кстати, это возникло как вопрос, внесенный пользователем на cppquiz.org. И этот сайт не следует воспринимать как источник передового опыта! ;)

knatten 03.09.2018 20:08

Интересна ссылка @ PeterA.Schneider. Кажется, это подразумевает, что на самом деле происходит то, что мертвый объект оригинал (с автоматической продолжительностью хранения) выходит за пределы области видимости, но тогда можно вызвать его деструктор, поскольку его место занял другой живой объект того же типа. (Это в отличие от чего-то вроде «новый объект также имеет автоматическую продолжительность хранения, и вызывается деструктор этого нового объекта.)

knatten 03.09.2018 20:13

Имена @knatten имеют область видимости, объекты (а переменные в стандарте - это объекты) имеют время жизни ;-). Время жизни объекта заканчивается (поскольку его память используется повторно). Однако пока еще ничего не вышло за рамки ... имя a (все еще в области видимости) относится к новому объекту, время существования которого началось, когда он был создан в новом месте размещения. В стандарте часто используется слово «переменная», которое в моей книге представляет собой комбинацию имени и записываемого объекта; в этих случаях повторного использования это соединение разрывается, и мы должны различать эти два. Есть ли где-нибудь определение «переменной»?

Peter - Reinstate Monica 03.09.2018 20:24

Переменная: «Именованный объект в области видимости»

Peter - Reinstate Monica 03.09.2018 20:31

Да, я был немного неточен. Дело в том, что class.dtor # 11.3 сообщает, что деструктор вызывается «для сконструированных объектов с автоматической продолжительностью хранения при выходе из блока, в котором создан объект». Таким образом, он явно ссылается на сам объект, а не на переменную с именем a. И в этом суть моего первоначального вопроса.

knatten 03.09.2018 20:32

@knatten Правильно - тогда объект - это тот, который вы поместили туда, где раньше принадлежал старый (к которому теперь относится имя a, согласно другим правилам).

Lightness Races in Orbit 04.09.2018 12:16

@knatten "Кроме того, что именно, по вашему мнению, вызывает вызов деструктора? 1: Тот факт, что имя a выходит за пределы области видимости и теперь ссылается на новый объект, или 2: Тот факт, что сам новый объект выходит за рамки? " И то, и другое. Я не думаю, что какое-либо различие между ними имеет смысл.

Lightness Races in Orbit 04.09.2018 12:17

Я думаю, это интересное различие. Второй объект уничтожается не потому, что блок, в котором он был создан, выходит, а потому, что блок, в котором был создан объект первый, завершается, и второй объект теперь повторно использовал эту память. Если мы извлечем new (&a) A; в функцию, это различие внезапно будет иметь практические последствия.

knatten 04.09.2018 13:08

@knatten Объем имени не изменился, и время жизни объекта привязано к области его имени, как и всегда. Просто он перенял это имя у другого, более старого, ныне мертвого объекта. Учитывая это, я не вижу никаких практических последствий различия между вашими 1 и 2. Единственная сложность возникает при замене объекта Другой!

Lightness Races in Orbit 04.09.2018 13:14

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

knatten 04.09.2018 13:28

@knatten Я тоже думаю, что мы в целом согласны. Есть больше академических способов осмысления ситуации, чем я предпочитаю использовать. Но я могу подтвердить, что объем названия a по-прежнему является ключевым фактором. Объекты не имеют области видимости; имена делать.

Lightness Races in Orbit 04.09.2018 13:35

Вот что я имел в виду, говоря о своем различении: godbolt.org/z/Y19_Ev. Но мы оба согласны с тем, что ключевым фактором здесь является объем a. Я думаю, что было бы полезно, если бы в стандарте было сказано что-то близкое к этому, а не говорилось, что первый объект должен быть уничтожен. Я думаю, что объяснение основного члена в ответе Ракете хорошее.

knatten 04.09.2018 13:42

@knatten В стандарте сказано и то, и другое. В нем говорится и о том, что выход имени за пределы области видимости приводит к уничтожению объекта, на который оно [в настоящее время] ссылается, и о том, что повторное использование имени завершает время жизни первоначально упомянутого объекта. Честно говоря, я не вижу здесь места для интерпретации.

Lightness Races in Orbit 04.09.2018 13:43

О, тогда я пропустил кое-что, что могло бы объяснить мое недоразумение! Где говорится, что имя, выходящее за пределы области видимости, приводит к уничтожению объекта, на который оно [в настоящее время] ссылается? Я знал только о class.dtor, и в нем обсуждается сам объект, а не его имя.

knatten 04.09.2018 13:46

@knatten Только очень быстро посмотрел, но, вероятно, [stmt.dcl] 2. По общему признанию, эта формулировка не так точна, как я ожидал. Я думаю, что это, вероятно, связано с понятием "можно было бы прояснить", которое член основной группы упомянул

Lightness Races in Orbit 04.09.2018 13:50

На самом деле, если посмотреть на это дальше, вся спецификация срока службы представляет собой полный беспорядок и нуждается в переписывании.

Lightness Races in Orbit 04.09.2018 13:53

Хм, [stmt.cld] ¶2 действительно помогает! Формулировка отличается от [class.dtor] (и, похоже, ближе к реальности).

knatten 04.09.2018 14:07

@LightnessRacesinOrbit: Что Стандарт должен был сделать много лет назад, так это отказаться от понятия что регионы хранения, которое полностью принадлежит пользовательскому коду, имеет типы [классы, не относящиеся к PODS, часто включают регионы, которые принадлежат реализации, и, следовательно, могут иметь типы]. Для целей оптимизации компиляторам необходимо знать, могут ли взаимодействовать различные доступы для хранилища, и это, в свою очередь, должно зависеть от способов получения и использования адресов (для объектов, которые доступны внешнему миру, или чей адрес берется , использование lvalue предполагает получение его адреса, а затем ...

supercat 11.09.2018 20:47

Типы хороши, не избавляйтесь от типов, а компиляторы делают больше, чем оптимизация, а оптимизация - не все, что имеет значение

Lightness Races in Orbit 11.09.2018 20:48

... используя этот адрес). Типы хороши, но они являются свойством указателей и ссылок, а не свойством базового хранилища.

supercat 11.09.2018 20:50

@supercat Нет, типы - это свойство значений. Хранилище хранит значения определенного типа. Вот для чего это нужно. Это гарантирует правильность и действительно важно.

Lightness Races in Orbit 11.09.2018 20:54

Если в любом месте юниверса существует только один указатель, который идентифицирует часть хранилища, и этот указатель преобразуется и сохраняется в uint32_t*, а оригинал стирается, есть ли причина, по которой хранилище должно инкапсулировать любое состояние за пределами значений бит там?

supercat 11.09.2018 20:55

@supercat: Да. Правильность. Мы не имеем дело с битами. C++ выше этого.

Lightness Races in Orbit 11.09.2018 20:59

@LightnessRacesinOrbit: для типов, не относящихся к PODS, хранилище которых частично принадлежит реализации, это очевидно. Какие бывают ситуации, когда запись области хранения с использованием типа PODS приведет к одному определенному поведению, а запись той же области хранения с другим типом PODS, имеющим тот же битовый шаблон, приведет к другому определенному поведению?

supercat 11.09.2018 21:13

Позвольте нам продолжить обсуждение в чате.

supercat 11.09.2018 21:36

@supercat Вы переместили стойки ворот, ограничив аргумент типами POD (хотя я подозреваю, что вы имеете в виду тривиальные типы)

Lightness Races in Orbit 12.09.2018 10:34

@LightnessRacesinOrbit: реализация C++ может претендовать на владение некоторыми хранилищами определенных типов, но не другими. Реализациям разрешено делать все, что они хотят, в любое время, с хранилищем, которым они владеют, и им разрешено вести себя произвольно, если такое хранилище будет нарушено в любое время. Им также разрешено (даже ожидается) использовать типы хранилищ для определения значения информации в хранилищах, которыми они владеют, и вести себя произвольно, если им предоставлена ​​неверная информация о таких типах. Прошу прощения, если я подразумевал несогласие с чем-либо из вышеперечисленного.

supercat 12.09.2018 17:13

@LightnessRacesinOrbit: с другой стороны, есть некоторые типы без определенной семантики, помимо множества объектов других типов, скрепленных вместе изолентой, и чей формат хранения указан как объединение рассматриваемых объектов, разделенных заполнением Достаточно для выравнивания. Существуют ли ситуации, когда для правильной генерации кода требуется, чтобы компилятор знал эффективный или динамический тип, связанный с таким хранилищем, и когда «знать, что он не знает» недостаточно?

supercat 12.09.2018 17:33

@supercat Я не знаю, и это не имеет особого значения, потому что «способность генерировать код» - это не причина, по которой существуют типы. Мы хотим, чтобы все это имело смысл. Я не изучил достаточно подробно ваши комментарии, чтобы сказать, имеет ли смысл ваша альтернатива, извините.

Lightness Races in Orbit 12.09.2018 17:50

Слишком долго для комментария.

Ответ Lightness правильный, и его ссылка является правильной ссылкой.

Но давайте разберемся с терминологией более подробно. Есть

  • «Продолжительность хранения», касающаяся памяти.
  • «Время жизни», касающееся объектов.
  • «Размах», по поводу имен.

Для автоматических переменных все три совпадают, поэтому мы часто не делаем четких различий: «переменная выходит за пределы области видимости». То есть: имя выходит за рамки; если это объект с автоматической продолжительностью хранения, вызывается деструктор, завершающий время жизни названного объекта; и наконец память высвобождается.

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

И нет, я думаю, вы не можете понимать «построенный» в 11.3 как «полностью построенный и не уничтоженный», потому что dtor будет называться опять таки (ошибочно), если время жизни объекта было преждевременно закончено предыдущим явным вызовом деструктора. Фактически, это одна из проблем, связанных с концепцией повторного использования памяти: если создание нового объекта не удается за исключением исключения, область видимости будет оставлена, и будет предпринята попытка вызова деструктора для неполного объекта или для старого объекта, который уже был удален.

Я полагаю, вы можете представить себе автоматически выделяемую типизированную память, помеченную тегом «для уничтожения», который оценивается при раскручивании стека. Среда выполнения C++ на самом деле не отслеживает отдельные объекты или их состояние за пределами этой простой концепции. Поскольку имена переменных в основном являются постоянными адресами, удобно думать о том, что «имя выходит за пределы области видимости», запускающее вызов деструктора для именованного объекта предполагаемого типа, предположительно присутствующего в этом месте. Если одно из этих предположений неверно, все ставки снимаются.

Спасибо за это разъяснение! Я согласен, что это, вероятно, намерение стандарта. Хотя, как видно из ответа @ Rakete1111, я думаю, что в стандарте это неясно. Я также согласен с вами по поводу «сконструированного» комментария, я просто пытался понять, что говорил Лайтнесс, так как думал, что он имел в виду именно это.

knatten 03.09.2018 22:30
Ответ принят как подходящий

Am I misreading the standard, or is this actually undefined behaviour?

Ничего подобного. В стандарте нет ничего неясного, но можно было бы его прояснить. Однако цель состоит в том, чтобы вызвать деструктор нового объекта, как подразумевается в [basic.life] p9.

[class.dtor] p12 не очень точен. Я спросил об этом Core и Майк Миллер (очень высокопоставленный член) сказал::

I wouldn't say that it's a contradiction [[class.dtor]p12 vs [basic.life]p9], but clarification is certainly needed. The destructor description was written slightly naively, without taking into consideration that the original object occupying a bit of automatic storage might have been replaced by a different object occupying that same bit of automatic storage, but the intent was that if a constructor was invoked on that bit of automatic storage to create an object therein - i.e., if control flowed through that declaration - then the destructor will be invoked for the object presumed to occupy that bit of automatic storage when the block is exited - even it it's not the "same" object that was created by the constructor invocation.

Я дополню этот ответ проблемой CWG, как только он будет опубликован. Итак, в вашем коде нет UB.

Спасибо! <3 Это точно, что меня смутило. После всех различных ответов и комментариев на этот вопрос и нашего предыдущего обмена электронной почтой я начал думать, что кто-то должен предложить разъяснение [class.dtor] p12. Так что я очень рад это слышать! :) Теперь я все-таки смогу сегодня поспать.

knatten 03.09.2018 22:21

@knatten Честно говоря, после некоторых размышлений я тоже обнаружил, что это совершенно неясно (граничит с противоречием), поэтому спасибо, что спросили здесь :)

Rakete1111 03.09.2018 22:23

Я все еще думаю, что [basic.life] p3 делает это очевидным.

Lightness Races in Orbit 04.09.2018 12:18

@Light В этом абзаце не говорится / не подразумевается, что вызывается деструктор нового объекта.

Rakete1111 04.09.2018 12:21

@ Rakete1111 Нет, но в нем говорится, что ни одно из правил того, что происходит с «объектами», не применяется после того, как время жизни указанного объекта закончилось, и мы можем понять, что нет вызова деструктора для старого, мертвого объекта (и, честно говоря, почему на земле было бы?). Остальное говорит нам, что все еще существует нормальный вызов деструктора того, что есть сейчас же в a.

Lightness Races in Orbit 04.09.2018 12:21

@Light Точно, я согласен. Но это будет означать, что деструктор вообще не вызывается.

Rakete1111 04.09.2018 12:23

@ Rakete1111 Почему? Теперь у нас есть новый объект. Теперь к этому применяются те же старые правила.

Lightness Races in Orbit 04.09.2018 13:13

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

Lightness Races in Orbit 04.09.2018 13:52

@Light Я не был уверен, стоит ли публиковать его имя, но ладно. Какие старые правила? [class.dtor] p12 не упоминает объекты с динамической продолжительностью хранения, которую имеет новый объект

Rakete1111 04.09.2018 14:59

@ Rakete1111 А? Хорошая точка зрения! Хм, тогда у нас может быть проблема

Lightness Races in Orbit 04.09.2018 15:10

@Light new всегда дает вам объект с длительностью динамического хранения :) (AFAIK)

Rakete1111 04.09.2018 15:14

@ Rakete1111 Обсуждали ли это когда-нибудь дальше? Насколько я могу судить, последний стандарт C++ (C++ 20) все еще имеет противоречие, упомянутое выше (то есть, что происходит, когда вы перезаписываете объект автоматической продолжительности хранения с помощью объекта динамического хранения, а исходный уничтоженный объект выходит из своей области ).

NotAProgrammer 09.10.2020 21:34

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

Как уже цитировалось:

"If a program ends the lifetime of an object of type T with static ([basic.stc.static]), thread ([basic.stc.thread]), or automatic ([basic.stc.auto]) storage duration and if T has a non-trivial destructor,39 the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place;"

Таким образом, после помещения B в хранилище a вам необходимо уничтожить B и снова поместить туда A, чтобы не нарушать приведенное выше правило. Это почему-то не применяется здесь напрямую, потому что вы подключаете A к A, но это показывает поведение. Это показывает, что это мышление ошибочно:

So the destructor for the original A object is invoked implicitly when main exits.

«Оригинального» объекта больше нет. В хранилище a есть только живой объект. И это все. И для данных, находящихся в данный момент в a, вызывается функция, а именно деструктор A. Это то, во что компилируется программа. Если бы он волшебным образом отслеживал все «оригинальные» объекты, вы бы каким-то образом имели динамическое поведение во время выполнения.

Кроме того:

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression ([expr.delete]) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

Поскольку деструктор A нетривиален и имеет побочные эффекты, (я думаю) его поведение undefined. Для встроенных типов это не применяется (следовательно, вы можете использовать буфер символов в качестве буфера объекта, не восстанавливая символы обратно в буфер после его использования), поскольку у них есть тривиальный (без операций) деструктор.

Я знаю, что оригинального объекта больше нет, именно поэтому я и разместил этот вопрос в первую очередь. Я считаю class.dtor¶11.3 тем, что реализация должна уничтожить исходный объект. Этот объект больше не существует, так что чтение явно неверно. Кажется, cwg согласен с тем, что формулировка неточная и ее необходимо обновить (см. Ответ @ Rakete1111).

knatten 04.09.2018 09:56

Что касается вашего абзаца «Дополнительно», я думаю, они говорят о том, что от реализации не требуется неявно вызывать деструктор когда вы повторно используете хранилище. Однако другие части стандарта говорят (или, по крайней мере, пытаются сказать), что деструктор будет вызываться при выходе из блока, в котором был создан a, и что этот деструктор теперь уничтожит новый A. Так что я не думаю, что здесь есть UB.

knatten 04.09.2018 09:59

@knatten Гарантируется, что один вызов деструктора для структуры A будет сделан при выходе из области видимости, потому что переменная типа A выходит за пределы области видимости. Мы несем ответственность за то, чтобы соответствующий объект типа A находился там в тот момент, когда это произошло (в противном случае его поведение не определено). Думаю, мы оба согласны с этим. Теперь к части с уничтожением "оригинального" объекта A. Когда вы запускаете программу, вы даже видите только 1 вызов деструктора, но 2 вызова конструктора. Вы можете обойтись без этого, потому что A - супер простая структура.

phön 04.09.2018 11:12

@knatten ... Представьте, например, что у вас есть структура с членом потока. Вы не можете просто скопировать новый объект поверх него (и это то, что в основном делает placemenet new). Перед повторным использованием хранилища необходимо вызвать деструктор вручную.

phön 04.09.2018 11:14

@curiousguy, я не понимаю, о чем ты

phön 04.09.2018 12:02

@ phön Если класс не предоставляет присваивание или какой-либо способ изменить его значение, что заставляет вас думать, что вы имеете право пытаться изменить значение экземпляра с помощью разрушения-построения?

curiousguy 04.09.2018 12:05

@curiousguy Ах. Это был просто пример, чтобы «показать», что вам не сойдет с рук это так легко, когда члены класса более сложные, чем тривиальный тип. Но отвечу на ваш вопрос: ну ничего не запрещает? Мы можем правильно уничтожить класс (где, например, живет std :: thread), но хранилище все еще там. Поэтому мы его повторно используем. Имейте в виду константные и ссылочные члены tho

phön 04.09.2018 12:14

Где гарантируется, что один вызов деструктора для структуры A выполняется при выходе из области видимости, поскольку переменная типа A выходит за пределы области видимости?

knatten 04.09.2018 13:49

@knatten Это основы работы с объектами в C++. В противном случае локальные переменные не работали бы.

Lightness Races in Orbit 04.09.2018 13:52

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

knatten 04.09.2018 14:04

@ phön Мы обсуждаем формулировки в стандарте и достаточно ли они описывают то, что происходит на практике. Демонстрация того, что происходит на практике, на самом деле ничего не добавляет к этому обсуждению.

knatten 04.09.2018 14:21

@knatten Вы сказали: «Я думаю, это гарантирует, что деструктор вызывается, потому что область, в которой был создан объект, выходит». И я привел пример, который показывает, что это явно не тот случай, когда используется показанная реализация (все проверенные компиляторы показывают такое поведение). Поведение, предусмотренное стандартом, несомненно, является тем, что вы видите здесь на практике, даже если мы не можем точно определить его прямо в формулировке.

phön 04.09.2018 14:27

Ваш пример этого не показывает. Размещение нового не дает автоматического срока хранения. Единственный объект с автоматической продолжительностью хранения, к которому применяется правило об автоматическом уничтожении в class.dtor, - это объект, созданный в строке 12. Таким образом, вывод из вашего примера соответствует как вашему заявлению, так и моему утверждению о том, что говорит стандарт.

knatten 04.09.2018 14:31

@knatten А как насчет этого: coliru.stacked-crooked.com/a/06f76dac548b7930. Я вижу, как трудно уточнить формулировку «переменная отвечает за автоматическое уничтожение». и я должен признать, что никогда не думал об этом. просто так оно и есть.

phön 04.09.2018 14:49

Да, это как раз причина для публикации этого вопроса, потому что в стандарте это недостаточно ясно. Мы все согласны с тем, что такое намерение стандарта и что на самом деле происходит на практике. И теперь кажется, что кто-то старший из Core также согласен с тем, что это нуждается в разъяснении, так что в будущем это может быть исправлено! :)

knatten 04.09.2018 21:03

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