C++ cout секвенирование побочных эффектов
Предположим, следующий фрагмент кода:
#include <iostream>
using namespace std;
char one()
{
cout << "one\n";
return '1';
}
char two()
{
cout << "two\n";
return '2';
}
int main(int,char**)
{
// 1:
cout << one()
<< '\n'
<< two()
<< '\n';
// 2:
operator<<(
operator<<(
operator<<(
operator<<(
cout,
one()),
'\n'),
two()),
'\n');
}
выполнение строк, помеченных как 1
а также 2
будучи скомпилированным с ideone делает то же самое, он печатает следующее:
two
one
1
2
С моей точки зрения, здесь мы наблюдаем неопределенное поведение, поскольку порядок, в котором разрешаются аргументы функции, не определен.
Это был вопрос на собеседовании, печатание выше заданной последовательности (без каких-либо альтернатив) должно было быть правильным ответом, но действительно ли это правильно?
2 ответа
Вы правы, и интервьюер показывает пугающе распространенное отсутствие понимания языка и его правил.
Эти две строки строго эквивалентны, если каждый operator<<
Для первой строки вызывается всегда свободная функция (стандарт гласит, что они есть).
Как вы правильно подумали, порядок между вызовами функций, за исключением случаев, когда аргументы одного являются возвращаемым значением другого, определяется неопределенным образом (до или после, но не определено, какое):
1.9 Выполнение программы
[intro.execution]
[...]
15 [...]
При вызове функции (независимо от того, является ли функция встроенной), каждое вычисление значения и побочный эффект, связанный с любым выражением аргумента или с выражением постфикса, обозначающим вызываемую функцию, упорядочивается перед выполнением каждого выражения или оператора в теле вызываемая функция. [Примечание: вычисления значений и побочные эффекты, связанные с различными выражениями аргументов, не являются последовательными. - примечание конца] Каждая оценка в вызывающей функции (включая другие вызовы функций), которая иначе специально не упорядочена до или после выполнения тела вызываемой функции, определяется неопределенным образом по отношению к выполнению вызываемой функции. 9 Несколько контекстов в C++ вызывает оценку вызова функции, даже если в блоке перевода не отображается соответствующий синтаксис вызова функции. [Пример: оценка нового выражения вызывает одну или несколько функций выделения и конструктора; см. 5.3.4. В другом примере вызов функции преобразования (12.3.2) может возникнуть в тех случаях, когда синтаксис вызова функции не появляется. - конечный пример] Ограничения последовательности выполнения вызываемой функции (как описано выше) являются признаками вызовов функции, которые оцениваются, независимо от того, каким может быть синтаксис выражения, вызывающего функцию.
Называя все части:
cout << one() // a) execute one() ("one\n")
// b) output the return-value ("1")
<< '\n' // c) output newline ("\n")
<< two() // d) execute two() ("two\n")
// e) output the return-value ("2")
<< '\n'; // f) output newline ("\n")
Ограничения заказа:
a < b < c < e < f
d < e < f
Или другое представление:
a < b < c <
< e < f
d <
Таким образом, все действующие полные заказы:
abcdef "one\n1\ntwo\n2\n"
abdcef "one\n1two\n\n2\n"
adbcef "one\ntwo\n1\n2\n"
dabcef "two\none\n1\n2\n"
Вы правы, но ответ на собеседование неверен.
Согласно абзацу §1.9/15 стандарта C++11:
За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными.
Например, это то, что производит Clang 3.4:
one
1
two
2