Когда именно инициализатор временно уничтожается?

Я построил этот эксперимент сегодня, ответив на некоторые вопросы.

struct A { 
  bool &b; 
  A(bool &b):b(b) { } 
  ~A() { std::cout << b; }  
  bool yield() { return true; } 
}; 

bool b = A(b).yield();

int main() { }

b имеет значение false (результат нулевой инициализации) перед установкой true с помощью динамической инициализации. Если временное устройство будет уничтожено до завершения инициализации b, мы напечатаем false, иначе true.

В спецификации сказано, что временное уничтожается в конце полного выражения. Это вроде не заказывается с инициализацией b. Так что мне интересно

  • Позволяет ли спецификация реализации печатать и false, и true в разных прогонах?

Clang печатает false для вышеуказанного, а GCC печатает true. Это меня смущает. Я пропустил какой-то текст спецификации, определяющий порядок?


person Johannes Schaub - litb    schedule 22.04.2011    source источник
comment
Чтобы уточнить, у b есть статическое хранилище?   -  person GManNickG    schedule 23.04.2011
comment
@GMan да. Прошу прощения за эту неясность. Я добавлю main функцию.   -  person Johannes Schaub - litb    schedule 23.04.2011
comment
Я увидел этот вопрос, вспомнил наш разговор по этому поводу и подумал: хм, я отправлю ссылку на litb и посмотрю, что он скажет по этому поводу. Неважно, а? :)   -  person Lightness Races in Orbit    schedule 23.04.2011
comment
Что Clang печатает в случае оператора присваивания внутри main?   -  person Potatoswatter    schedule 23.04.2011
comment
@Tomalak, я просто просмотрел свои вопросы и нашел пару хороших ответов, которые я принял.   -  person Johannes Schaub - litb    schedule 23.04.2011
comment
@Potatoswatter: Нерелевантно (imo), поскольку это определенно будет UB.   -  person ildjarn    schedule 23.04.2011
comment
@ildjarn присвоение будет полным выражением, поэтому bool b; b = A(b).yield(); будет прекрасно и напечатает true.   -  person Johannes Schaub - litb    schedule 23.04.2011
comment
@litb: Вы пропустили мою веселую шутку!   -  person Lightness Races in Orbit    schedule 23.04.2011
comment
Согласно логике в моем ответе (если он правильный, re: §1.9 / 13), b будет неинициализирован при запуске деструктора A.   -  person ildjarn    schedule 23.04.2011
comment
@ildjarn на самом деле не зависит от того, инициализирован b или нет. Ссылки просто должны быть инициализированы допустимыми объектами или функциями. Объект не нужно инициализировать.   -  person Johannes Schaub - litb    schedule 23.04.2011
comment
@Johannes Schaub: Временный A будет уничтожен до инициализации b, поэтому std::cout << b; будет отправлять неинициализированный объект cout; это не УБ?   -  person ildjarn    schedule 23.04.2011
comment
Зарегистрированная ошибка против clang: llvm.org/bugs/show_bug.cgi?id=9783   -  person Johannes Schaub - litb    schedule 23.04.2011
comment
@Johannes: Будет интересно посмотреть, что скажут ребята из Clang, но моя немедленная реакция такова, что у вас неопределенное поведение, так что это не ошибка.   -  person Jerry Coffin    schedule 23.04.2011
comment
@Jerry, у них действительно не было окончательного мнения, когда мы обсуждали это на канале IRC. Один парень сказал, что поведение либо не определено, либо что clang неверно (потому что, очевидно, поведение clang не имеет для него смысла). Думаю, все согласились с тем, что спецификация не ясна.   -  person Johannes Schaub - litb    schedule 23.04.2011
comment
@Jerry просмотреть журналы ideone.com/Whtfk   -  person Johannes Schaub - litb    schedule 23.04.2011
comment
Забавно, что Johannes Schaub - litb получает так много Upvotes на свои вопросы, но не так много ответов. Ха-ха ..   -  person Sadique    schedule 23.04.2011


Ответы (3)


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

Истинная или ложная часть (как вы сказали) состоит в том, что уничтожение временного объекта A не упорядочено относительно динамической инициализации b.

Совершенно ничего не может быть связано с тем, что инициализация b не упорядочена относительно создания / инициализации std::cout; когда вы пытаетесь уничтожить временное, cout, возможно, еще не был создан / инициализирован, поэтому попытка распечатать что-то может вообще не сработать. [Изменить: это характерно для C ++ 98/03 и не относится к C ++ 11.]

Изменить: вот как я, по крайней мере, вижу последовательность:

введите описание изображения здесь

Edit2: перечитав §12.2 / 4 (еще раз), я снова изменил диаграмму. В §12.2 / 4 говорится:

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

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

Это также довольно ясно (я думаю), что временное удержание true не должно быть уничтожено в конце полного выражения, поэтому я перерисовал диаграмму, чтобы это отразить.

Этот раздел отсутствует в C ++ 0x / C ++ 11, поэтому я перерисовал диаграмму (еще раз), чтобы показать разницу между ними (и насколько эта часть стала проще в C ++ 11). .

person Jerry Coffin    schedule 22.04.2011
comment
+1 (когда я получу больше голосов за 16 минут) за указание на то, что cout может даже не существовать. - person Lightness Races in Orbit; 23.04.2011
comment
@Jerry, если я это сделаю bool b = true; b = false;, значит ли это, что я веду неопределенное поведение? Потому что я не вижу заказов на запись в b. В C ++ 03 кажется, что они могут возникать между одними и теми же точками последовательности, а в C ++ 0x они кажутся двумя неупорядоченными побочными эффектами. - person Johannes Schaub - litb; 23.04.2011
comment
@litb: два оператора с точкой последовательности между ними, не так ли? - person Lightness Races in Orbit; 23.04.2011
comment
Может литб имел ввиду bool b = true, b = false;? - person ildjarn; 23.04.2011
comment
@tomalak нет, я действительно это имел в виду. Насколько я могу судить, в спецификации нигде не говорится, что между двумя операторами есть точка последовательности. Также спецификация C ++ 0x нигде не говорит о том, что два оператора упорядочены в соответствии с отношением «упорядочено до». РЕДАКТИРОВАТЬ: Но в спецификации C ++ 0x говорится, что операторы выполняются последовательно. Это был бы очень небрежный способ сказать «последовательность» перед лексически следующими операторами или чем-то подобным. И поскольку C ++ 03 говорит то же самое, я думаю, что это определенно не предназначенное средство для выполнения описанного выше поведения. - person Johannes Schaub - litb; 23.04.2011
comment
@litb: Разве это не сделало бы a++; std::cout << a; UB? Я неправильно понимаю, что такое statement? - person Lightness Races in Orbit; 23.04.2011
comment
@Tomalak, поскольку a++ и std::cout << a оба являются полными выражениями, они оба будут разделены точками последовательности (C ++ 03) или оба будут установлены в отношении в соответствии с последовательностью до (C ++ 0x). - person Johannes Schaub - litb; 23.04.2011
comment
@litb: Если bool b = true; b = false; - UB, то, на мой взгляд, это серьезный недостаток стандарта. - person Lightness Races in Orbit; 23.04.2011
comment
Re cout: C ++ 0x гарантирует, что если вы включите <iostream>, то cout будет сконструирован перед любым его использованием в этом TU IIRC после #include. Я предполагал такую ​​гарантию в своем вопросе :) - person Johannes Schaub - litb; 23.04.2011
comment
@Johannes: он говорит (C ++ 03, §1.9 / 16): есть точка последовательности при завершении оценки каждого полного выражения. В сочетании с операторами, выполняемыми по порядку, это, кажется, дает ваше bool b=true; b=false; примерное поведение. N3242 §1.9 / 14 использует другую формулировку, но дает, по сути, ту же гарантию. Проблема здесь в том, что у нас есть две отдельные вещи, которые должны произойти (уничтожение A () и инициализация b), но без упорядочения по сравнению друг с другом. - person Jerry Coffin; 23.04.2011
comment
Что касается cout, я использовал C ++ 03. C ++ 0x несколько ужесточает требования, чтобы дать это определенное поведение. - person Jerry Coffin; 23.04.2011
comment
@Jerry полное выражение в bool b = true true. Запись в b не является частью какого-либо выражения. Итак, почему существует точка последовательности при завершении оценки каждого полного выражения. подать заявление? -.- Я думаю, что меня сбивает с толку все косвенное указание в спецификации. - person Johannes Schaub - litb; 23.04.2011
comment
@ Йоханнес: Извини, ты прав. Я думаю, что это все еще определено. Это статическая инициализация, которая должна выполняться перед любой динамической инициализацией (§3.6.2 / 1). Тогда динамическая инициализация должна либо предшествовать выполнению чего-либо в main, либо любому использованию функции или объекта в TU (§3.6.2 / 3). Мне нужно было бы проверить, чтобы быть уверенным, но я считаю, что присвоение квалифицируется как использование, что будет означать, что статическая инициализация должна быть выполнена до того, как может быть выполнено назначение. Поскольку b=false; является присваиванием, статическая инициализация должна быть выполнена, прежде чем она сможет выполняться. Хотя формулировка неаккуратная. - person Jerry Coffin; 23.04.2011
comment
@Jerry Я имею в виду, если у вас есть местный житель, например int main() { bool b = true; b = false; }. Другой код, чем в моем ответе, без глобальных переменных и т.п. - person Johannes Schaub - litb; 23.04.2011
comment
@Johannes: В этом случае, да, я думаю, все, что вы получаете, это то, что оператор объявления и оператор присваивания выполняются по порядку. N3242 §6.2 / 1 действительно говорит дать немного более точную формулировку: все побочные эффекты от оператора выражения завершаются до выполнения следующего оператора. - person Jerry Coffin; 23.04.2011
comment
@Jerry, но bool b = true не является выражением -.- - person Johannes Schaub - litb; 23.04.2011
comment
@Johannes: Ой, да, я не уверен, как я пропустил эту незначительную деталь! Вы правы: остается только за исключением указанного, операторы выполняются последовательно. Хотя я думаю, что цель этого (довольно) ясна, это определенно оставляет желать лучшего в плане точности. - person Jerry Coffin; 23.04.2011
comment
Повторная диаграмма: я не уверен, откуда узлы создают временную копию true и уничтожают temp bool (я не думаю, что в моем коде есть какое-то bool временное), но во всем остальном на диаграмме я согласен с вами . - person Johannes Schaub - litb; 23.04.2011
comment
Разумеется, я бы нарисовал инициализацию b с результатом yield () параллельно с концом полного выражения, как я объяснил в комментарии к ответу @Tomalak. - person Johannes Schaub - litb; 23.04.2011
comment
@Johannes: После повторного прочтения я перерисовал диаграмму (еще раз) и добавил цитату, которая (я думаю) поддерживает то, как она нарисована в настоящее время. - person Jerry Coffin; 23.04.2011
comment
Однако 12.2p4 относится только к временным. В нашем случае временного нет. Итак, в этом контексте временный объект, содержащий результат выражения, должен сохраняться ... не может быть осмысленно применен. Его можно применять только к выражениям типа класса, поскольку те, которые возвращаются из функций, являются временными (12.2p1). Принципиально нет смысла говорить о временном для таких вещей, как bool() или bool f() { return true; }, потому что что временно? Значение true несущественно. И нет никакого объекта. Временное значение в спецификации относится к ограниченному времени жизни объектов. - person Johannes Schaub - litb; 23.04.2011
comment
Мне не полностью ясно, применимо ли 12.2 / 4 или нет. Предыдущие абзацы конкретно относятся к временным объектам типа класса, тогда как 12.2 / 4 говорит только о временных объектах. Возможно, они считали само собой разумеющимся, что к тому времени вы понимаете, что это только объекты класса, или они могли иметь в виду, что этот абзац может применяться независимо от того, относится ли объект к типу класса или нет. В N3242 весь этот раздел был удален. - person Jerry Coffin; 23.04.2011
comment
@Jerry, весь текст 12.2 / 4 пропал в C ++ 0x, поэтому любое обсуждение его работоспособности имеет только ограниченное применение :) Но на самом деле, конечно, могут быть временные типы неклассовых типов, но они создаются сама реализация (когда ей нужен адрес чего-либо, например, если вы делаете bool const& b = true;, она создает временное значение типа bool и привязывает к нему b). Нет ничего плохого в том, чтобы сказать «временно» в 12.2 / 4. Если они позже добавят другие типы временных файлов, им не нужно будет менять 12.2 / 4. Но в любом случае 12.2 / 4 в этой форме исчезли в C ++ 0x. - person Johannes Schaub - litb; 23.04.2011
comment
Я думаю, что, учитывая текущую формулировку, его (по модулю правила as-if) необходимо создать временный bool, содержащий значение true, и выполнить инициализацию из его копии. Вероятно, это не то, что было задумано, но это то, что мне кажется в этом разделе на самом деле. - person Jerry Coffin; 23.04.2011
comment
@Jerry Я думаю, что нигде не сказано, что возврат prvalue функции, которая возвращает неклассовый тип, является временным. Где вы говорите, это требует? - person Johannes Schaub - litb; 23.04.2011
comment
Я не знаю - как я уже сказал, весь этот раздел исчез, и я не вижу аналога в C ++ 11. Я перерисовал диаграмму (снова!), Чтобы показать разницу. - person Jerry Coffin; 23.04.2011
comment
@Jerry, похоже, вы так говорите, поскольку у вас разные диаграммы для C ++ 11 и C ++ 03, а C ++ 03 говорит, что где-то будет временное значение bool, но нет никакой разницы в этих спецификациях для это. Если yield() вернет ссылку на bool или тип, отличный от bool, в спецификациях будет разница. Но по мере написания кода обе спецификации должны иметь полностью идентичные диаграммы. - person Johannes Schaub - litb; 23.04.2011
comment
@Johannes: Я не согласен - здесь явно есть разница в спецификациях. Нельзя избежать того факта, что это инициализатор для декларатора, который определяет объект, поэтому применяется 12.2 / 4 в C ++ 03, даже если он, возможно, не был предназначен для этого. Поскольку prvalue вообще не определен в C ++ 03, он явно не может / не применяется там. - person Jerry Coffin; 23.04.2011
comment
@Jerry, это инициализатор для декларатора, определяющего объект. А временный объект, содержащий результат выражения, должен сохраняться до завершения инициализации объекта. Поскольку в нашем случае нет временного объекта, содержащего результат выражения инициализатора, нам вообще ничего не нужно делать. Вы говорите, что в shared_ptr<int> a; bool b = a; a рассматривается как временный, и создается временный shared_ptr<int>, а затем bool инициализируется из временного? - person Johannes Schaub - litb; 23.04.2011
comment
Во всяком случае, время сна для меня :) Но думаю, дело ясное. - person Johannes Schaub - litb; 23.04.2011
comment
Я ничего не говорю о shared_ptr. Но 12.2 / 4 не делает временное необязательным - он не говорит, есть ли временный ..., он говорит, что временное, что ..., что (мне) не кажется, что оставляет место для решив не создавать временный. - person Jerry Coffin; 23.04.2011
comment
+1. Иногда я говорю: «Какую запутанную паутину мы плетем», но я не имею в виду буквально! - person Potatoswatter; 23.04.2011

(Цитата из стандарта C ++ 03)

Сначала идет §12.2 / 3:

Когда реализация вводит временный объект класса, который имеет нетривиальный конструктор (12.1), она должна гарантировать, что конструктор вызывается для временного объекта. Точно так же деструктор должен вызываться для временного с нетривиальным деструктором (12.4). Временные объекты уничтожаются на последнем этапе оценки полного выражения (1.9), которое (лексически) содержит точку, в которой они были созданы. Это верно, даже если эта оценка заканчивается выдачей исключения.

Я считаю, что это отвлекающий маневр из-за §1.9 / 13:

[Примечание: определенные контексты в C ++ вызывают вычисление полного выражения, которое является результатом синтаксической конструкции, отличной от выражения (5.18). Например, в 8.5 один синтаксис для инициализатора:

    ( expression-list )

но результирующая конструкция является вызовом функции для функции-конструктора со списком-выражением в качестве списка аргументов; такой вызов функции является полным выражением. Например, в 8.5 другой синтаксис инициализатора:

    = initializer-clause

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

Это подразумевает, что A(b).yield() сам по себе является полным выражением, поэтому §12.2 / 3 здесь неуместен.

Затем мы переходим к пунктам последовательности - §1.9 / 7:

Доступ к объекту, обозначенному изменчивым lvalue (3.10), изменение объекта, вызов функции ввода-вывода библиотеки или вызов функции, которая выполняет любую из этих операций, - все это побочные эффекты, которые представляют собой изменения в состоянии среды выполнения. Оценка выражения может вызвать побочные эффекты. В определенных точках последовательности выполнения, называемых точками последовательности, все побочные эффекты предыдущих оценок должны быть завершены, и никаких побочных эффектов последующих оценок не должно происходить.

§1.9/16:

По завершении оценки каждого полного выражения есть точка последовательности.

и §1.9 / 17:

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

Собирая все вместе, я думаю, что Clang прав, а GCC (и MSVC 2010 SP1) неверен - временный объект, содержащий результат выражения (срок жизни которого продлевается согласно §12.2 / 4 ) - это bool, возвращаемый из A::yield(), а не временный A, на котором вызывается yield. Принимая во внимание §1.9, после вызова A::yield() должна быть точка последовательности, во время которой временный A уничтожается.

person ildjarn    schedule 22.04.2011

Во-первых, просто чтобы прояснить абзац, который был здесь ранее, использование b в его собственной (динамической) инициализации здесь не является UB. Перед вычислением выражения b не инициализируется, а инициализируется нулем.


Временное A должно жить ровно столько, сколько полное выражение:

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

[ИСО / МЭК 14882: 2003 (E) 12.2 / 3]

Строка bool b = A(b).yield(); - это объявление, которое является утверждением, а не выражением. Выражение под рукой находится только в правой части =. [ИСО / МЭК 14882: 2003 (E) A.6]

Это означало бы, что временное должно быть уничтожено до динамической инициализации, не так ли? Конечно, значение true сохраняется во временном элементе, который содержит результат выражения 1, до завершения инициализации, но исходный временный A должен быть уничтожен до фактического изменения b.

Поэтому я ожидал результата false каждый раз.


1

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

[ИСО / МЭК 14882: 2003 (E) 12.2 / 4]

person Lightness Races in Orbit    schedule 22.04.2011
comment
Я понимаю, что b инициализируется нулем до false до того, как произойдет указанная инициализация. Тем не менее, не использует ли он его в собственном инициализаторе по-прежнему UB?: Нет, поскольку b имеет статическую продолжительность хранения, обращение к себе во время динамической инициализации нормально, поскольку гарантированно будет иметь уже были инициализированы нулями. Если b был определен / инициализирован таким образом в локальной области (например, внутри main), или если он был инициализирован константным выражением, относящимся к самому себе, это будет UB. - person ildjarn; 23.04.2011
comment
ааа, в этом есть большой смысл. Но я думаю, что нигде не сказано, что инициализация b должна происходить до или после полной точки последовательности выражения. Что, если реализация инициализирует b сразу после возврата yield(), но перед уничтожением временного A? Временное по-прежнему будет уничтожено на последнем этапе оценки полного выражения. - person Johannes Schaub - litb; 23.04.2011
comment
@ildjarn: я знаю, что у него уже будет значение от нулевой инициализации, но я не могу найти ни одного пункта относительно этого UB, в котором говорится, что он делает различие для динамической инициализации. Я, наверное, просто скучаю по нему. Все еще смотрящий. - person Lightness Races in Orbit; 23.04.2011
comment
@Tomalak Geret'kal: Для меня это подразумевается формулировкой §3.6.2 / 1 - person ildjarn; 23.04.2011
comment
@ildjarn: я не согласен, хотя я предполагаю, что, поскольку во всех случаях, обсуждаемых в этом сценарии, терминология похожа на использование неинициализированного объекта, мы можем принять неинициализированный как статически-неинициализированный, если объект статический. - person Lightness Races in Orbit; 23.04.2011
comment
@Tomalak Geret'kal: Но в том-то и дело - динамическая инициализация b не использует никаких неинициализированных объектов, поскольку b гарантированно будет инициализирован нулем к моменту динамической инициализации. То есть порядок здесь гарантирован, и b не будет неинициализирован, а будет инициализирован нулем. - person ildjarn; 23.04.2011