Подтвердить что ты не робот

Безопасно ли вводить операторы increment/decment внутри тернарных/условных операторов?

Здесь пример

#include <iostream>
using namespace std;
int main()
{   
    int x = 0;
    cout << (x == 0 ? x++ : x) << endl; //operator in branch
    cout << "x=" << x << endl;
    cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition
    cout << "x=" << x << endl;
    return 0;
}

выход:

0
x=1
1
x=1

Я понимаю вывод, но это поведение undefined или нет? Гарантирован ли порядок оценки в любом случае?

Даже если это гарантировано, я уверен, что использование increment/decment может быстро стать проблемой для удобочитаемости. Я только спрашиваю, как я видел аналогичный код, и сразу был не уверен, учитывая, что существует множество примеров двусмысленного / undefined использования операторов increment/decment, таких как...

  • С++ не определяет порядок оценки параметров функции.

    int nValue = Add(x, ++x);
    
  • Язык С++ говорит, что вы не можете изменять переменную более одного раза между точками последовательности.

     x = ++y + y++
    
  • Поскольку операторы increment and decment имеют побочные эффекты, использование выражений с операторами увеличения или уменьшения в макропроцессоре может иметь нежелательные результаты.

     #define max(a,b) ((a)<(b))?(b):(a)
     k = max( ++i, j );
    
4b9b3361

Ответ 1

Для условного оператора (§5.16 [expr.cond]/p1):

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

Для логического оператора OR (§5.15 [expr.log.or]/p1-2):

второй операнд не оценивается, если первый операнд оценивает значение true. [...] Если второе выражение оценивается, каждое вычисление значения и побочный эффект, связанный с первым выражением, секвенирован до каждое вычисление значения и побочный эффект, связанный со вторым выражение.

Поведение вашего кода четко определено.

Ответ 2

Существует гарантированный порядок выполнения в тройных операторах и логических операциях && и ||, поэтому в точках последовательности оценки нет конфликта.

Один за раз

 cout << (x == 0 ? x++ : x) << endl; //operator in branch

Всегда будет выводить x, но увеличит его, только если он равен 0.

 cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

Это также хорошо определено, если x равно 1, он не будет оценивать RHS, если бы он не уменьшал его, но --x никогда не будет 0, так что это будет true iff x == 1, в этом случае x также будет 0.

В последнем случае, если x является INT_MIN, это не является корректным поведением для его уменьшения (и оно будет выполняться).

Это не может произойти в первом случае, когда x не будет 0, если это INT_MAX, чтобы вы были в безопасности.

Ответ 3

Я понимаю вывод, но это поведение undefined или нет?

Код отлично определен. В стандарте C11 говорится:

6.5.15 Условный оператор

Первый операнд оценивается; существует точка последовательности между ее оценкой и оценка второго или третьего операнда (в зависимости от того, что оценивается). Второй операнд оценивается только в том случае, если первое сравнение неравномерно с 0; третий операнд оценивается только в том случае, если первый сравнивается с 0; результатом является значение второго или третьего операнда (в зависимости от того, что оценивается), преобразуется в тип, описанный ниже .110)

6.5.14 Логический оператор OR

В отличие от побитового оператора | оператор || гарантирует оценку слева направо; если второй операнд оценивается, имеется точка последовательности между оценками первого и второй операнд. Если первый операнд сравнивается неравномерно с 0, второй операнд Не Оценено.

Далее wiki объясняет это с помощью примера:

  • Между оценкой левого и правого операндов && (логический И), || (логический ИЛИ) (как часть оценки короткого замыкания) и comma operators. Например, в выражении *p++ != 0 && *q++ != 0 все побочные эффекты подвыражения *p++ != 0 завершаются до любой попытки доступа к q.

  • Между оценкой первого операнда троичного оператора "вопросительный знак" и второго или третьего операнда. Например, в выражении a = (*p++) ? (*p++) : 0 имеется точка последовательности после первого *p++, то есть она уже была увеличена к тому времени, когда выполняется второй экземпляр.

Правило для || и ?: одинаково для С++ (разделы 5.15 и 5.16), как в C.


Является ли порядок оценки гарантированным в любом случае?

Да. Порядок оценки операндов операторов ||, &&, , и ?: гарантированно будет слева направо.

Ответ 4

В C хранимое значение объекта может быть изменено только один раз между двумя точками последовательности.

Возникает точка последовательности:

  • В конце полного выражения.
  • В операциях &&, || и ?:
  • При вызове функции.

Так, например, это выражение x = i++ * i++ равно undefined, тогда как x = i++ && i++ является совершенно законным.

В вашем коде показано определенное поведение.

int x = 0;

cout < (x == 0? x ++: x) < епсИ;

В приведенном выше выражении x есть 0, поэтому x++ будет выполняться, здесь x ++ - это пошаговое приращение, поэтому он выведет 0.

cout < "x =" < x < епсИ;

В приведенном выше выражении x теперь имеет значение 1, поэтому выход будет 1.

cout < (x == 1 || --x == 0≤1: 2) < епсИ;

Здесь x есть 1, поэтому следующее условие не оценивается (--x == 0), а выход будет 1.

cout < "x =" < x < епсИ;

Поскольку выражение --x == 0 не оценивается, результат снова будет 1.

Ответ 5

Да, безопасно использовать операторы increment/decment, как вы. Вот что происходит в вашем коде:

Фрагмент # 1

cout << (x == 0 ? x++ : x) << endl; //operator in branch

В этом фрагменте вы тестируете, если x == 0, который true. Поскольку это true, ваше тернарное выражение оценивает x++. Поскольку вы используете пост-приращение здесь, исходное значение для x печатается в стандартный выходной поток, , а затем x увеличивается.

Фрагмент # 2

cout << (x == 1 || --x == 0 ? 1 : 2) << endl; //operator in condition

Этот фрагмент немного запутан, но он все же дает предсказуемый результат. На этом этапе x = 1 из первого фрагмента. В тройном выражении сначала оценивается условная часть; однако, из-за Short-Circuiting, второе условие --x == 0 никогда не оценивается.

Для С++ операторы || и && являются короткозамкнутыми булевыми операторами для логических OR и логических AND соответственно. Когда вы используете эти операторы, ваши условия проверяются (слева направо), пока не будет определен окончательный результат. Как только результат будет определен, больше условий не будет проверено.

Глядя на фрагмент №2, ваше первое условие проверяет, есть ли x == 1. Поскольку ваше первое условие оценивается как true, и вы используете логическое ИЛИ, нет необходимости продолжать оценивать другие условия. Это означает, что --x == 0 никогда не выполняется.


Быстрая заметка о коротком замыкании:

Короткое замыкание полезно для повышения производительности в вашей программе. Предположим, что у вас есть такое состояние, которое вызывает несколько дорогостоящих функций:

if (task1() && task2())
{ 
    //...Do something...
}

В этом примере task2 никогда не следует вызывать, пока task1 не завершится успешно (task2 зависит от некоторых данных, которые были изменены task1).

Поскольку мы используем оператор короткого замыкания AND, если task1 завершается с ошибкой, возвращая false, тогда оператор if имеет достаточную информацию, чтобы выйти из системы раньше и прекратить проверку других условий. Это означает, что task2 никогда не вызывается.