Когда точно инициализатор временно уничтожен?
Я построил этот эксперимент сегодня, после ответа на какой-то вопрос
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
. Это меня смущает. Пропустил ли я некоторый текст спецификации, определяющий порядок?
Ответы
Ответ 1
Я думаю, что это позволяло печатать истинные, или ложные, или по нескольким не связанным причинам, ничего.
Истинная или ложная часть (как вы сказали), что уничтожение временного объекта A
не упорядочено относительно динамической инициализации b
.
Ничто не возможно, потому что инициализация b
не упорядочена относительно создания/инициализации std::cout
; когда вы пытаетесь уничтожить временные, cout
, возможно, еще не были созданы/инициализированы, поэтому попытка распечатать что-то может не работать в этой точке вообще. [Изменить: это относится к С++ 98/03 и не относится к С++ 11.]
Изменить: вот как я, по крайней мере, вижу последовательность:
![enter image description here]()
Edit2: После перечитывания §12.2/4 (опять же), я снова изменил диаграмму. В §12.2/4 говорится:
Существует два контекста, в которых временные объекты уничтожаются в другой точке, чем конец полного выражения. Первый контекст - это когда выражение появляется как инициализатор для декларатора, определяющего объект. В этом контексте временное выражение, которое содержит результат выражения, сохраняется до завершения инициализации объектов. Объект инициализируется из копии временного; во время этого копирования реализация может вызвать конструктор копирования много раз; временное уничтожается после его копирования до или после завершения инициализации.
Я считаю, что это выражение является инициализатором для декларатора, определяющего объект, поэтому в этом случае требуется инициализировать объект из копии значения выражения (true
), а не непосредственно из возвращаемого значения. В случае true
это, вероятно, является различием без разницы, но я думаю, что диаграмма технически более точна, поскольку она стоит прямо сейчас.
Это также довольно ясно (я думаю), что временное удерживание true
не должно быть уничтожено в конце полного выражения, поэтому я снова нарисовал диаграмму, чтобы отразить это.
Этот раздел отсутствует в С++ 0x/С++ 11, поэтому я снова нарисовал диаграмму (еще раз), чтобы показать разницу между двумя (и насколько проще эта часть появилась в C + +11).
Ответ 2
(Цитирование стандарта С++ 03)
Сначала найдутся §12.2/3:
Когда реализация вводит временный объект класса, который имеет нетривиальный конструктор (12.1), он должен гарантировать, что конструктор вызывается для временного объекта. Аналогичным образом деструктор должен быть вызван для временного с нетривиальным деструктором (12.4). Временные объекты уничтожаются как последний шаг при оценке полного выражения (1.9), который (лексически) содержит точку, в которой они были созданы. Это справедливо, даже если эта оценка заканчивается тем, что бросает исключение.
Я считаю, что это красная селедка, из-за §1.9/13:
[Примечание: некоторые контексты в С++ вызывают оценку полного выражения, которое получается из синтаксической конструкции, отличной от выражения (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
будет уничтожена.
Ответ 3
Во-первых, просто чтобы очистить абзац, который был здесь ранее, использование b
в его собственной (динамической) инициализации здесь не UB. Прежде чем выражение будет оценено, b
не инициализируется, но инициализируется нулем.
Временная A
должна жить точно столько же, сколько полное выражение:
Временные объекты уничтожаются как последний шаг в оценке полное выражение (1.9), которое (лексически) содержит точку, в которой они были создан.
[ISO/IEC 14882: 2003 (R) 12.2/3]
Строка bool b = A(b).yield();
- это объявление, которое является выражением, которое не является выражением. Выражение под рукой обнаруживается только для RHS =
. [ISO/IEC 14882: 2003 (E) A.6]
Это означало бы, что временное должно быть уничтожено до того, как произойдет динамическая инициализация, нет? Конечно, значение true
сохраняется во временном, которое содержит результат выражения 1 до тех пор, пока инициализация не завершится, но исходный A
временный должен быть уничтожен до того, как b
будет фактически изменен.
Поэтому я ожидал бы выход false
каждый раз.
1
Первый контекст - это когда выражение появляется как инициализатор для декларатора, определяющего объект. В этом контексте временное содержит результат выражения должны сохраняться до тех пор, пока объекты инициализация завершена "
[ISO/IEC 14882: 2003 (R) 12.2/4]