Обязательны ли короткие замыкания логических операторов? А порядок оценки?

Требует ли стандарт ANSI, чтобы логические операторы были закорочены, в C или C++?

Я запутался, вспомнив книгу K&R, в которой говорилось, что ваш код не должен зависеть от короткого замыкания этих операций, поскольку они могут не зависеть. Может ли кто-нибудь указать, где в стандарте говорится, что логические операции всегда закорочены? Я в основном заинтересован в C++, и ответ на C был бы отличным.

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

Указывает ли стандарт порядок оценки этого выражения?

if( functionA() && functionB() && functionC() ) cout<<"Hello world";

7 ответов

Решение

Да, для операторов требуется короткое замыкание и порядок оценки || а также && в стандартах C и C++.

Стандарт C++ гласит (в стандарте C должен быть эквивалентный пункт):

1.9.18

При оценке следующих выражений

a && b
a || b
a ? b : c
a , b

используя встроенное значение операторов в этих выражениях, существует точка последовательности после вычисления первого выражения (12).

В C++ есть дополнительная ловушка: короткое замыкание НЕ применяется к типам, которые перегружают операторы || а также &&,

Сноска 12. Операторы, указанные в этом параграфе, являются встроенными операторами, как описано в разделе 5. Когда один из этих операторов перегружен (раздел 13) в допустимом контексте, обозначая, таким образом, определяемую пользователем функцию оператора, выражение обозначает вызов функции, и операнды формируют список аргументов, без подразумеваемой точки последовательности между ними.

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

Оценка короткого замыкания и порядок оценки являются обязательным семантическим стандартом в C и C++.

Если бы это было не так, код, подобный этому, не был бы обычной идиомой.

   char* pChar = 0;
   // some actions which may or may not set pChar to something
   if ((pChar != 0) && (*pChar != '\0')) {
      // do something useful

   }

Раздел 6.5.13 Логический оператор И спецификации C99 (ссылка PDF) говорит

(4). В отличие от побитового двоичного оператора &, оператор && гарантирует оценку слева направо; после оценки первого операнда есть точка последовательности. Если первый операнд сравнивается равным 0, второй операнд не оценивается.

Аналогично, в разделе 6.5.14 оператор логического ИЛИ говорит:

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

Подобную формулировку можно найти в стандартах C++, см. Раздел 5.14 в этом черновом варианте. Как отмечают контролеры в другом ответе, если вы переопределите && или ||, то оба операнда должны быть оценены, поскольку это становится обычным вызовом функции.

Да, это требуется (как порядок оценки, так и короткое замыкание). В вашем примере, если все функции возвращают true, порядок вызовов строго из functionA, затем functionB и затем functionC. Используется для этого как

if(ptr && ptr->value) { 
    ...
}

То же самое для оператора запятой:

// calls a, then b and evaluates to the value returned by b
// which is used to initialize c
int c = (a(), b()); 

Один говорит между левым и правым операндом &&, ||, , и между первым и вторым / третьим операндом ?: (условный оператор) является "точкой последовательности". Любые побочные эффекты оцениваются полностью до этого момента. Итак, это безопасно:

int a = 0;
int b = (a++, a); // b initialized with 1, and a is 1

Обратите внимание, что оператор запятой не следует путать с синтаксической запятой, используемой для разделения вещей:

// order of calls to a and b is unspecified!
function(a(), b());

Стандарт C++ говорит в 5.14/1:

Операторы && группируются слева направо. Оба операнда неявно преобразуются в тип bool (пункт 4). Результат равен true, если оба операнда имеют значение true, в противном случае - false. В отличие от &, && гарантирует оценку слева направо: второй операнд не оценивается, если первый операнд является ложным.

И в 5.15/1:

|| оператор группы слева направо. Оба операнда неявно преобразуются в bool (пункт 4). Он возвращает true, если любой из его операндов равен true, и false в противном случае. В отличие от |, || гарантирует оценку слева направо; более того, второй операнд не оценивается, если первый операнд оценивается как true.

Это говорит для обоих рядом с тем:

Результатом является бул. Все побочные эффекты первого выражения, за исключением уничтожения временных (12.2), происходят до того, как будет вычислено второе выражение.

В дополнение к этому, 1.9/18 говорит

При оценке каждого из выражений

  • a && b
  • a || b
  • a ? b : C
  • a , b

используя встроенное значение операторов в этих выражениях (5.14, 5.15, 5.16, 5.18), после вычисления первого выражения существует точка последовательности.

Прямо из старого доброго K&R:

С гарантирует, что && а также || оцениваются слева направо - мы скоро увидим случаи, когда это имеет значение.

Будьте очень, очень осторожны.

Для основных типов это операторы быстрого доступа.

Но если вы определяете эти операторы для своего собственного класса или типа перечисления, они не являются ярлыками. Из-за этого семантического различия в их использовании в этих различных обстоятельствах рекомендуется не определять эти операторы.

Для operator && а также operator || для фундаментальных типов порядок оценки слева направо (иначе быстрая обработка была бы трудна:-) Но для перегруженных операторов, которые вы определяете, это в основном синтаксический сахар для определения метода, и, следовательно, порядок оценки параметров не определен.

Если вы доверяете Википедии:

[&& а также ||] семантически отличаются от побитовых операторов & и | потому что они никогда не оценят правый операнд, если результат можно определить только по левому

http://en.wikipedia.org/wiki/C_(programming_language)

Ваш вопрос сводится к приоритетности оператора C++ и ассоциативности. По сути, в выражениях с несколькими операторами и без скобок компилятор создает дерево выражений, следуя этим правилам.

Для приоритета, когда у вас есть что-то вроде A op1 B op2 CВы можете сгруппировать вещи как (A op1 B) op2 C или же A op1 (B op2 C), Если op1 имеет более высокий приоритет, чем op2Вы получите первое выражение. В противном случае вы получите второй.

Для ассоциативности, когда у вас есть что-то вроде A op B op CСнова можно сгруппировать как (A op B) op C или же A op (B op C), Если op оставил ассоциативность, мы в конечном итоге с первым выражением. Если у него правильная ассоциативность, мы получим второй. Это также работает для операторов с тем же уровнем приоритета.

В этом конкретном случае && имеет более высокий приоритет, чем ||поэтому выражение будет оценено как (a != "" && it == seqMap.end()) || isEven,

Сам порядок "слева направо" в форме дерева выражений. Итак, мы сначала оценим a != "" && it == seqMap.end(), Если это правда, все выражение верно, в противном случае мы идем к isEven, Процедура повторяется рекурсивно внутри левого подвыражения, конечно.


Интересные лакомые кусочки, но понятие приоритета имеет свои корни в математических обозначениях. То же самое происходит в a*b + c, где * имеет более высокий приоритет, чем +,

Еще более интересный / неясный, для непонятного выражения A1 op1 A2 op2 ... opn-1 Anгде все операторы имеют одинаковый приоритет, количество деревьев двоичных выражений, которые мы могли сформировать, задается так называемыми каталонскими числами. Для больших nони растут очень быстро. d

Другие вопросы по тегам