Я учусь о перегрузках операторов в C ++, и я вижу , что ==
и !=
просто некоторые специальные функции , которые могут быть настроены для определенного пользователя типов. Однако меня беспокоит, почему нужны два отдельных определения? Я думал, что если a == b
это правда, то a != b
автоматически ложно, и наоборот, и нет другой возможности, потому что, по определению, a != b
есть !(a == b)
. И я не мог представить себе ситуацию, в которой это было бы неправдой. Но, может быть, мое воображение ограничено или я чего-то не знаю?
Я знаю, что могу определить одно в терминах другого, но я не об этом. Я также не спрашиваю о разнице между сравнением объектов по значению или идентичности. Или два объекта могут быть равными и не равными одновременно (это определенно не вариант! Эти вещи взаимоисключающие). Я спрашиваю о следующем:
Возможна ли ситуация, когда задавать вопросы о том, что два объекта равны, имеет смысл, но спрашивать о том, что они не равны, не имеет смысла? (либо с точки зрения пользователя, либо с точки зрения исполнителя)
Если такой возможности нет, то почему в C ++ эти два оператора определены как две разные функции?
источник
'undefined' != expression
это всегда true (или false, или undefined), независимо от того, можно ли вычислить выражение. В этом случаеa!=b
вернул бы правильный результат согласно определению, но!(a==b)
потерпел бы неудачу, еслиb
не может быть оценен. (Или займет много времени, если оценкаb
стоит дорого).(NaN != NaN) == true
Ответы:
Вы бы не хотели язык автоматически переписать в
a != b
виде ,!(a == b)
когдаa == b
возвращает нечто иное , чемbool
. И есть несколько причин, почему вы могли бы сделать это.У вас могут быть объекты построителя выражений, где
a == b
нет и не предназначено для какого-либо сравнения, а просто создается представление узла выраженияa == b
.Вы можете иметь ленивую оценку, где
a == b
нет и не предполагается выполнять какое-либо сравнение напрямую, но вместо этого возвращает какой-то вид,lazy<bool>
который может быть преобразован вbool
неявно или явно позднее, чтобы фактически выполнить сравнение. Возможно в сочетании с объектами построителя выражений для полной оптимизации выражения перед оценкой.У вас может быть некоторый пользовательский
optional<T>
шаблонный класс, где указаны необязательные переменныеt
иu
, вы хотите разрешитьt == u
, но сделайте так, чтобы он возвращалсяoptional<bool>
.Вероятно, есть еще кое-что, о чем я не думал. И хотя в этих примерах операция
a == b
иa != b
оба имеют смысл, все жеa != b
это не одно и то же!(a == b)
, поэтому необходимы отдельные определения.источник
!=
вместо двух проходов . Особенно в то время, когда нельзя было полагаться на компилятор, чтобы объединить циклы. Или даже сегодня, если вам не удастся убедить компилятор, ваши векторы не пересекаются.==
!
!
может также построить некоторое выражение узел , и мы все еще в порядке заменыa != b
с!(a == b)
, до сих пор , как идет. То же самое касаетсяlazy<bool>::operator!
, он может вернутьсяlazy<bool>
.optional<bool>
является более убедительным, поскольку логическая истинность, например,boost::optional
зависит от того, существует ли значение, а не от самого значения.Nan
s - пожалуйста, помнитеNaN
s;Потому что вы можете перегружать их, и, перегружая их, вы можете придать им совершенно иной смысл, чем их первоначальный.
Возьмем, к примеру, оператор
<<
, первоначально побитовый оператор сдвига влево, теперь обычно перегруженный как оператор вставки, например, вstd::cout << something
; совершенно другой смысл от оригинального.Таким образом, если вы принимаете, что значение оператора изменяется при его перегрузке, то нет никаких причин препятствовать пользователю давать значение оператору
==
, которое не совсем отрицает оператор!=
, хотя это может сбивать с толку.источник
==
и!=
существуют в виде различных операторов. С другой стороны, они, вероятно, не существуют как отдельные операторы, потому что вы можете перегружать их отдельно, но из-за устаревших и удобных (краткость кода) причин.Вам не нужно определять оба.
Если они являются взаимоисключающими, вы все равно можете быть краткими, только определив
==
и<
наряду с std :: rel_opsСоотношение:
Мы часто связываем эти операторы с равенством.
Хотя именно так они ведут себя на фундаментальных типах, не обязательно, чтобы это было их поведение на пользовательских типах данных. Вам даже не нужно возвращать бул, если вы этого не хотите.
Я видел, как люди перезагружали операторов причудливыми способами, но обнаружил, что это имеет смысл для их приложений, специфичных для конкретной области. Даже если интерфейс показывает, что они являются взаимоисключающими, автор может захотеть добавить определенную внутреннюю логику.
Я знаю, что вы хотите конкретный пример,
так что вот один из фреймворков Catch для тестирования, который я считаю практичным:
Эти операторы делают разные вещи, и не имеет смысла определять один метод как! (Не) другого. Причина, по которой это сделано, заключается в том, что платформа может распечатать сделанное сравнение. Для этого необходимо захватить контекст того, какой перегруженный оператор был использован.
источник
std::rel_ops
? Большое спасибо за указание на это.rel_ops
все равно ужасноЕсть некоторые очень хорошо известные соглашения, в которых
(a == b)
иоба(a != b)
являютсяложными,не обязательно противоположными. В частности, в SQL любое сравнение с NULL дает NULL, а не true или false.Вероятно, не стоит создавать новые примеры этого, если это вообще возможно, потому что это не интуитивно понятно, но если вы пытаетесь смоделировать существующее соглашение, было бы неплохо иметь возможность заставить ваши операторы вести себя «правильно» для этого контекст.
источник
NULL == something
возвращать Unknown, и вы также хотелиNULL != something
бы возвращать Unknown, и вы хотели бы!Unknown
вернутьUnknown
. И в этом случае реализацияoperator!=
как отрицаниеoperator==
все еще правильна.operator==
либоoperator!=
, но не другое, либо 2) реализоватьoperator!=
способом, отличным от отрицанияoperator==
. И реализация логики SQL для значений NULL не является таковой.Я отвечу только на вторую часть вашего вопроса, а именно:
Одной из причин, по которой имеет смысл разрешить разработчику перегружать оба, является производительность. Вы можете разрешить оптимизацию путем реализации обоих
==
и!=
. Тогдаx != y
может быть дешевле, чем!(x == y)
есть. Некоторые компиляторы могут оптимизировать его для вас, но, возможно, нет, особенно если у вас есть сложные объекты с большим количеством ветвлений.Даже в Хаскеле, где разработчики очень серьезно относятся к законам и математическим концепциям, все равно разрешается перегружать и то,
==
и другое/=
, как вы можете видеть здесь ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html # v: -61--61- ):Вероятно, это будет считаться микрооптимизацией, но в некоторых случаях это может быть оправдано.
источник
pcmpeqb
инструкция, но нет инструкции упакованного сравнения, создающей маску! =. Поэтому, если вы не можете просто изменить логику того, что использует результаты, вы должны использовать другую инструкцию, чтобы инвертировать его. (Забавный факт: у набора инструкций AMD XOP действительно есть упакованное сравнениеneq
. Жаль, что Intel не приняла / не расширила XOP; есть некоторые полезные инструкции в этом ISA-расширении, которое скоро станет мертвым.)PXOR
со всеми единицами для инвертирования результата маски сравнения) в узком цикле может иметь значение.x == y
стоят значительно больше, чемx != y
. Вычисление последнего может быть значительно дешевле из-за предсказания ветвлений и т. Д.Это мнение. Может быть, это не так. Но разработчики языка, не будучи всезнающими, решили не ограничивать людей, которые могут придумывать ситуации, в которых это может иметь смысл (по крайней мере, для них).
источник
В ответ на редактирование;
В общем , нет, это не имеет смысла. Равенство и реляционные операторы обычно бывают множествами. Если есть равенство, то и неравенство; меньше чем, то больше чем и т. д. и
<=
т. д. Аналогичный подход применяется и к арифметическим операторам, они также обычно входят в естественные логические множества.Об этом свидетельствует
std::rel_ops
пространство имен. Если вы реализуете операторы равенства и меньше чем, использование этого пространства имен дает вам другие, реализованные в терминах ваших оригинальных реализованных операторов.Все это говорит о том, существуют ли условия или ситуации, когда одно не будет означать сразу другое или не может быть реализовано с точки зрения других? Да , возможно, их немного, но они есть; опять же, о чем свидетельствует
rel_ops
существование собственного пространства имен. По этой причине, позволяя им быть реализованными независимо, вы можете использовать язык, чтобы получить семантику, которая вам нужна или нужна, таким образом, который все еще естественен и интуитивен для пользователя или клиента кода.Упомянутая ленивая оценка уже является отличным примером этого. Другим хорошим примером является предоставление им семантики, которая вовсе не означает равенство или неравенство. Аналогичный пример , чтобы это операторы сдвига битов
<<
и>>
используется для вставки и извлечения потока. Хотя это может вызывать недовольство в общих кругах, в некоторых областях, относящихся к конкретной области, это может иметь смысл.источник
Если
==
и!=
операторы фактически не подразумевают равенство, таким же образом , что<<
и>>
операторы потока не предполагают битовый сдвиг. Если вы относитесь к символам так, как будто они означают какую-то другую концепцию, они не должны быть взаимоисключающими.С точки зрения равенства, может иметь смысл, если ваш вариант использования требует рассматривать объекты как несопоставимые, так что каждое сравнение должно возвращать false (или несопоставимый тип результата, если ваши операторы возвращают non-bool). Я не могу придумать конкретную ситуацию, в которой это оправдано, но я видел, что это достаточно разумно.
источник
С большой властью приходит великая ответственность или, по крайней мере, действительно хорошие руководства по стилю.
==
и!=
может быть перегружен, чтобы делать все, что угодно Это и благословение, и проклятие. Там нет никакой гарантии, что!=
значит!(a==b)
.источник
Я не могу оправдать перегрузку этого оператора, но в приведенном выше примере невозможно определить
operator!=
как «противоположность»operator==
.источник
!=
это действительно не означает противоположность==
.==
?В конце вы проверяете с помощью этих операторов, что выражение
a == b
илиa != b
возвращает логическое значение (true
илиfalse
). Эти выражения возвращают логическое значение после сравнения, а не являются взаимоисключающими.источник
Следует учитывать, что может быть возможность реализовать один из этих операторов более эффективно, чем просто использовать отрицание другого.
(Мой пример здесь был мусором, но точка зрения остается неизменной, вспомним, например, фильтры Блума: они позволяют проводить быстрое тестирование, если что-то не входит в набор, но тестирование, если оно входит, может занять гораздо больше времени.)
И это ваша ответственность как программиста, чтобы удержать это. Вероятно, хорошая вещь, чтобы написать тест для.
источник
!((a == rhs.a) && (b == rhs.b))
не допускается короткое замыкание? если!(a == rhs.a)
, то(b == rhs.b)
не будет оцениваться.==
, он прекратит сравнение, как только первые соответствующие элементы не будут равны. Но в случае!=
, если бы он был реализован в терминах==
, ему сначала нужно было бы сравнить все соответствующие элементы (когда они все равны), чтобы можно было сказать, что они не равны: P Но при реализации, как в В приведенном выше примере он прекратит сравнение, как только найдет первую неравную пару. Отличный пример действительно.!((a == b) && (c == d))
и(a != b) || (c != d)
эквивалентны с точки зрения эффективности короткого замыкания.Настраивая поведение операторов, вы можете заставить их делать то, что вы хотите.
Вы можете настроить вещи. Например, вы можете настроить класс. Объекты этого класса можно сравнить, просто проверив определенное свойство. Зная, что это так, вы можете написать некоторый конкретный код, который проверяет только минимальные вещи, вместо проверки каждого бита каждого отдельного свойства во всем объекте.
Представьте себе случай, когда вы можете понять, что что-то отличается так же быстро, если не быстрее, чем вы можете обнаружить, что что-то такое же. Конечно, как только вы выясните, является ли что-то одинаковым или другим, вы можете узнать противоположное, просто слегка перевернув. Однако переключение этого бита является дополнительной операцией. В некоторых случаях, когда код многократно выполняется, сохранение одной операции (умноженное на много раз) может привести к общему увеличению скорости. (Например, если вы сохраняете одну операцию на пиксель мегапиксельного экрана, то вы только что сохранили миллион операций. Умножьте на 60 экранов в секунду, и вы сохраните еще больше операций.)
Ответ HVD предоставляет несколько дополнительных примеров.
источник
Да, потому что один означает «эквивалентный», а другой означает «неэквивалентный», и эти термины являются взаимоисключающими. Любое другое значение для этих операторов сбивает с толку, и его следует избегать любыми способами.
источник
a != b
это не равно по!(a == b)
этой причине?Может быть, несравнимое правило, где
a != b
было ложным иa == b
был ложным , как немного без гражданства.источник
operator==()
иoperator!=()
не обязательноbool
, они могут быть перечислением, которое включает в себя состояние без состояния, если вы этого хотите, и все же операторы могут все еще быть определены, так что(a != b) == !(a==b)
держится ..