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

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

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

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

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

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

Ответы [ 7 ]

141 голосов
/ 10 марта 2009

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

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

1.9.18

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

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

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

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

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

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

70 голосов
/ 10 марта 2009

Оценка короткого замыкания и порядок оценки являются обязательным семантическим стандартом в 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). В отличие от побитового двоичного оператора &, оператор && гарантирует оценка слева направо; Eсть точка последовательности после оценки первый операнд Если первый операнд сравнивается равным 0, второй операнд не оценивается.

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

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

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

17 голосов
/ 10 марта 2009

Да, это требуется (как порядок оценки, так и короткое замыкание). В вашем примере, если все функции возвращают 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.

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

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

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

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

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

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

10 голосов
/ 10 марта 2009

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

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

5 голосов
/ 10 марта 2009

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

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

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

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

0 голосов
/ 16 ноября 2016

Ваш вопрос сводится к приоритету оператора 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 они растут очень быстро. д * * одна тысяча тридцать семь

0 голосов
/ 10 марта 2009

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

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

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

...