Чи дозволено коротке замикання логічних операторів? А порядок оцінювання?


140

Чи зобов’язаний стандарт ANSI дозволити логічним операторам бути короткозамкненим або в C, або C ++?

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

Я також пам’ятаю, що читав (не пам'ятаю, де), що порядок оцінювання не є чітко визначеним, тому ваш код не повинен залежати або припускати, що функції в виразі будуть виконуватися в певному порядку: до кінця оператора всі посилаються функції буде викликано, але компілятор має свободу вибору найбільш ефективного замовлення.

Чи вказує стандарт порядок оцінки цього виразу?

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

12
Обережно: Це стосується типів POD. Але якщо ви перевантажуєте оператора && або оператора || для певного класу це НЕ Я повторюю НЕ ярлик. Ось чому радимо НЕ визначати цих операторів для власних класів.
Мартін Йорк

Я переосмислив ці оператори деякий час тому, коли створив клас, який би виконував основні булеві алгебри. Напевно, слід дотримуватися попереджувального коментаря "це руйнує коротке замикання та ліво-праву оцінку!" якщо я забуду це. Також перевантажили * / + і зробили їм свої синоніми :-)
Джо Пінеда

Функціонування викликів у блоці if-блоку не є хорошою практикою програмування. Завжди має змінну, оголошену, яка містить значення повернення методу, і використовувати його у блоці if.
СР Чайтаня

6
@SRChaitanya Це невірно. Те, що ви довільно описуєте як погану практику, робиться постійно, особливо з функціями, які повертають булеві, як тут.
Маркіз Лорн

Відповіді:


154

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

Стандарт C ++ говорить (у стандарті C повинно бути рівнозначне застереження):

1.9.18

В оцінці наступних виразів

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

використовуючи вбудоване значення операторів у цих виразах, є точка послідовності після оцінки першого виразу (12).

У C ++ є додаткова пастка: коротке замикання НЕ застосовується до типів, які перевантажують операторів ||і &&.

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

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


3
Не знав, що коротке замикання не застосовуватиметься до перевантажених логічних операцій, це є метою. Чи можете ви додати посилання на стандарт чи джерело? Я вам не довіряю, просто хочу дізнатися більше про це.
Джо Пінеда

4
так, це логічно. він виступає в якості аргументів оператору && (a, b). саме його реалізація говорить про те, що відбувається.
Йоханнес Шауб - ліб

10
žarb: Передати b оператору && (a, b) просто неможливо, не оцінюючи його. І немає можливості скасувати оцінку b, оскільки компілятор не може гарантувати відсутність побічних ефектів.
jmucchiello

2
Мені це сумно. Я б подумав, що, якщо я перероблю операторів && та || і вони все ще повністю детерміновані , компілятор виявить це і буде тримати їх оцінку в короткому замиканні: зрештою, порядок не має значення, вони не гарантують побічних ефектів!
Джо Пінеда

2
@Joe: але повернене значення та аргументи оператора можуть змінюватися від булевого до чогось іншого. Раніше я реалізовував "спеціальну" логіку з ТРИма значеннями ("true", "false" і "unknown"). Повернене значення є детермінованим, але поведінка короткого замикання не є доцільною.
Алекс Б

70

Оцінка короткого замикання та порядок оцінювання є обов'язковим семантичним стандартом як для 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 у цій копії проекту . Як зазначають шашки в іншій відповіді, якщо ви переосмислюєте && або ||, обидва операнди повинні бути оцінені, оскільки це стає звичайним викликом функції.


Ах, що я шукав! Гаразд, і порядок оцінки, і коротке замикання призначені відповідно до ANSI-C 99! Я б дуже хотів побачити еквівалентну посилання для ANSI-C ++, хоча я майже на 99%, це повинно бути те саме.
Джо Пінеда

Важко знайти хороше безкоштовне посилання для стандартів C ++, я посилався на чернетку копії, яку я знайшов із деяким googling.
Пол Діксон

Вірно для типів POD. Але якщо ви перевантажуєте оператора && або оператора || це не ярлик.
Мартін Йорк

1
так, цікаво відзначити, що для bool ви завжди матимете гарантований порядок оцінки та поведінку короткого замикання. тому що ви не можете перевантажувати оператора && для двох вбудованих типів. вам потрібно хоча б один визначений користувачем тип в операндах, щоб він поводився по-різному.
Йоханнес Шауб - ліб

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

19

Так, це вимагає (як порядок оцінки, так і коротке замикання). У вашому прикладі, якщо всі функції повертаються як істинні, порядок викликів суворо функціонуєA, а потім функціяB, а потім функціяC. Використовується для подібного

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). Результат вірний, якщо обидва операнди істинні та помилкові в іншому випадку. На відміну від &, && гарантує оцінку зліва направо: другий операнд не оцінюється, якщо перший операнд помилковий.

І в 5.15/1:

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

Він говорить і для тих, хто поруч із цими:

Результат - бул. Всі побічні ефекти першого вираження, за винятком руйнування темпорацій (12.2), трапляються до того, як буде оцінено другий вираз.

Крім того, 1.9/18каже

В оцінці кожного з виразів

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

використовуючи вбудоване значення операторів у цих виразах (5.14, 5.15, 5.16, 5.18), після оцінки першого виразу є точка послідовності.


10

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

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


3
K&R 2-е видання p40. "Вирази, пов'язані з && або ||, оцінюються зліва направо, і оцінка припиняється, як тільки з'ясовується істинність або хибність результату. Більшість програм C покладаються на ці властивості." Я не можу знайти твій цитований текст ніде в книзі. Це з надзвичайно застарілого 1-го видання? Поясніть, будь ласка, де ви знайшли цей текст.
Лундін

1
Добре, виходить, ви цитуєте цей стародавній підручник . Це з 1974 року і вкрай не має значення.
Лундін

6

Будьте дуже дуже обережні.

Для основних типів це оператори швидкого доступу.

Але якщо ви визначите ці оператори для власного класу або типів перерахування, вони не є ярликом. Через цю смислову різницю в їх використанні за цих різних обставин рекомендується не визначати цих операторів.

Для operator &&та operator ||для основних типів порядок оцінки зліва направо (інакше коротке скорочення було б важким :-) Але для перевантажених операторів, які ви визначаєте, це в основному синтаксичний цукор для визначення методу, і таким чином порядок оцінки параметрів є невизначений.


1
Перевантаження оператора не має нічого спільного з тим, чи є тип POD або ні. Щоб визначити функцію оператора, принаймні один з аргументів повинен бути класом (або структурою або об'єднанням), або перерахунком, або посиланням на один із них. Бути POD означає, що ви можете використовувати memcpy на ньому.
Дерек Ледбеттер

І це я казав. Якщо ви перевантажуєте && для свого класу, то це дійсно лише виклик методу. Таким чином, ви не можете розраховувати на порядок оцінки параметрів. Очевидно, що ви не можете перевантажувати && для типів POD.
Мартін Йорк

3
Ви неправильно використовуєте термін "типи POD". Ви можете перевантажувати && для будь-якої структури, класу, союзу чи переліку, ПОД або ні. Ви не можете перевантажувати &&, якщо обидві сторони є числовими типами або покажчиками.
Дерек Ледбеттер

Я використовував POD як (char / int / float і т. Д.), А не агрегатний POD (про що ви говорите), і зазвичай його посилають окремо або більш чітко, оскільки це не вбудований тип.
Мартін Йорк

2
Отже, ви мали на увазі "фундаментальні типи", але написали "типи POD"?
Öö Tiib

0

Ваше питання зводиться до пріоритету оператора 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

Якщо ви довіряєте Вікіпедії:

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

C (мова програмування)


11
Навіщо довіряти wiki, коли у нас є стандарт!
Мартін Йорк

1
Якщо ви довіряєте Wikipedia, "Wikipedia не є надійним ресурсом" .
Маркіз Лорн

Це справедливо, наскільки це йдеться, але неповно, оскільки перевантажені оператори в C ++ не мають короткого замикання.
Томас Падрон-Маккарті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.