Всі процеси проектування призводять до компромісів між взаємно несумісними цілями. На жаль, процес проектування перевантаженого &&оператора в C ++ дав заплутаний кінцевий результат: те, що сама функція, від якої ви хочете, &&- її поведінка в короткому замиканні - опущена.
Деталі того, як цей процес дизайну опинився в цьому нещасному місці, ті, кого я не знаю. Однак важливо подивитися, як пізніше процес проектування врахував цей неприємний результат. У C # перевантажений &&оператор має коротке замикання. Як дизайнери C # домоглися цього?
Одна з інших відповідей пропонує «лямбда-ліфтинг». Це є:
A && B
можна реалізувати як щось морально рівнозначне:
operator_&& ( A, ()=> B )
де другий аргумент використовує певний механізм для ледачої оцінки, щоб при оцінці вироблялися побічні ефекти та значення виразу. Реалізація перевантаженого оператора зробить лише ледачу оцінку лише в разі необхідності.
Це не те, що зробила команда дизайнерів C #. ( В стороні: хоча лямбда - ліфтинг є те , що я зробив , коли прийшов час , щоб зробити вираз деревовидного уявлення про ??оператора, який вимагає певних конверсійних операцій повинен виконуватися ліниво Описуючи , що в деталях б , однак, головним екскурс Досить сказати: .. Лямбда - ліфтинг працює, але є достатньо важкою, що ми хотіли цього уникнути.)
Скоріше, рішення C # розбиває проблему на дві окремі проблеми:
- чи слід оцінювати правий операнд?
- якщо відповідь на вищесказане було «так», то як ми поєднаємо два операнди?
Тому проблема вирішується шляхом незаконного перевантаження &&безпосередньо. Скоріше, в C # ви повинні перевантажити двох операторів, кожен з яких відповідає на одне з цих двох питань.
class C
{
// Is this thing "false-ish"? If yes, we can skip computing the right
// hand size of an &&
public static bool operator false (C c) { whatever }
// If we didn't skip the RHS, how do we combine them?
public static C operator & (C left, C right) { whatever }
...
(Окрім: насправді, три. C # вимагає, що якщо оператор falseнадається, тоді trueтакож повинен бути наданий оператор , який відповідає на питання: чи ця річ є "справжньою?"? вимагає обох.)
Розглянемо виклад форми:
C cresult = cleft && cright;
Компілятор генерує код для цього, як вважав, що ви написали цей псевдо-C #:
C cresult;
C tempLeft = cleft;
cresult = C.false(tempLeft) ? tempLeft : C.&(tempLeft, cright);
Як бачите, ліва частина завжди оцінюється. Якщо він визначається як "помилковий результат", то це результат. В іншому випадку оцінюється права частина, і викликається прагнення визначеного користувачем оператора &.
||Оператор визначено в аналогічному образі, як виклик оператора істинний і нетерплячий |оператор:
cresult = C.true(tempLeft) ? tempLeft : C.|(tempLeft , cright);
Визначивши всі чотири оператори - true, false, &і |- C # дозволяє не тільки сказати , cleft && crightале і без короткого замикання cleft & cright, а також if (cleft) if (cright) ..., і , c ? consequence : alternativeі while(c), і так далі.
Тепер я сказав, що всі дизайнерські процеси є результатом компромісу. Тут дизайнерам мови C # вдалося отримати коротке замикання &&і ||правильно, але це вимагає перевантаження чотирьох операторів замість двох , що дехто вважає заплутаним. Функція true / false для оператора - одна з найменш добре зрозумілих особливостей у C #. Ціль володіння розумною та зрозумілою мовою, яка знайома користувачам C ++, протистояла бажанню короткого замикання та прагненню не застосовувати лямбда-ліфтинг чи інші форми ледачого оцінювання. Я думаю , що це розумний компроміс положення, але важливо розуміти , що це є компромісом положення. Просто інше компромісна позиція, ніж посадили дизайнери C ++.
Якщо тема мовного дизайну для таких операторів вас зацікавила, розгляньте, прочитавши мою серію про те, чому C # не визначає цих операторів на незмінні булеві:
http://ericlippert.com/2012/03/26/null-is-not-false-part-one/
operator&&(const Foo& lhs, const Foo& rhs) : (lhs.bars == 0)