Когда именно временно уничтожается инициализатор? - PullRequest
17 голосов
/ 23 апреля 2011

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

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.Это смущает меня.Я пропустил какой-то текст спецификации, определяющий порядок?

Ответы [ 3 ]

8 голосов
/ 23 апреля 2011

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

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

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

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

enter image description here

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

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

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

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

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

2 голосов
/ 23 апреля 2011

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


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

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

[ISO / IEC 14882: 2003 (E) 12.2 / 3]

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

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

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


1

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

[ISO / IEC 14882: 2003 (E) 12.2 / 4]

2 голосов
/ 23 апреля 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:

Доступ к объекту, обозначенному volatile 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 уничтожается.

...