В C ++ 20 введены сравнения по умолчанию, также известные как «космический корабль»operator<=> , которые позволяют запрашивать </ <=/ ==/ !=/ >=/ и / или >операторы, созданные компилятором, с очевидной / наивной (?) Реализацией ...
auto operator<=>(const MyClass&) const = default;
... но вы можете настроить это для более сложных ситуаций (обсуждаемых ниже). См. Здесь предложение по языку, которое содержит обоснования и обсуждение. Этот ответ остается актуальным для C ++ 17 и более ранних версий, а также для понимания того, когда следует настраивать реализацию operator<=>....
Может показаться немного бесполезным для C ++ не стандартизировать это ранее, но часто в структурах / классах есть некоторые элементы данных, которые нужно исключить из сравнения (например, счетчики, кешированные результаты, емкость контейнера, код успешной / ошибки последней операции, курсоры), так как а также решения по бесчисленным вопросам, включая, но не ограничиваясь:
- какие поля сравнивать в первую очередь, например, сравнение конкретного
intчлена может очень быстро устранить 99% неравных объектов, в то время как map<string,string>член может часто иметь идентичные записи и быть относительно дорогостоящим для сравнения - если значения загружаются во время выполнения, программист может иметь представление о компилятор не может
- при сравнении строк: чувствительность к регистру, эквивалентность пробелов и разделителей, соглашения об экранировании ...
- точность при сравнении чисел с плавающей запятой / удвоением
- следует ли считать значения NaN с плавающей запятой равными
- сравнение указателей или данных с указанием (и, если последнее, как узнать, относятся ли указатели к массивам и сколько объектов / байтов требует сравнения)
- имеет ли значение порядок при сравнении несортированных контейнеров (например
vector, list), и если да, можно ли сортировать их на месте перед сравнением или использовать дополнительную память для сортировки временных контейнеров каждый раз, когда выполняется сравнение
- сколько элементов массива в настоящее время содержат допустимые значения, которые следует сравнивать (есть ли где-то размер или дозорный?)
- какой член а
unionсравнивать
- нормализация: например, типы даты могут допускать выход за пределы диапазона дня месяца или месяца года, или рациональный / дробный объект может иметь 6/8, а другой - 3/4, что по соображениям производительности они исправляют лениво с отдельным шагом нормализации; вам может потребоваться решить, запускать ли нормализацию перед сравнением
- что делать, если слабые указатели недействительны
- как обрабатывать элементы и базы, которые не реализуются
operator==сами по себе (но могут иметь compare()или operator<или str()или геттеры ...)
- какие блокировки должны быть приняты при чтении / сравнении данных, которые другие потоки могут захотеть обновить
Так что неплохо иметь ошибку, пока вы явно не подумаете о том, что сравнение должно означать для вашей конкретной структуры, вместо того, чтобы позволять ей компилироваться, но не давать значимого результата во время выполнения .
Все , что сказал, что было бы хорошо , если C ++ позволит вам сказать , bool operator==() const = default;когда ты решил «наивный» член-на-член ==испытание было в порядке. То же самое для !=. Учитывая несколько членов / баз, « по умолчанию» <, <=, >, и >=реализации кажутся безнадежными , хотя - каскадные на основе порядка декларации возможно , но вряд ли будет то , что хотел, учитывая противоречивые императивы для упорядочения членов (основания быть обязательно перед членами, группировка по доступность, строительство / разрушение до зависимого использования). Чтобы быть более широко полезным, C ++ потребовалась бы новая система аннотаций элементов данных / баз для выбора - это было бы здорово иметь в Стандарте, хотя в идеале в сочетании с созданием определяемого пользователем кода на основе AST ... Я ожидаю Это'
Типичная реализация операторов равенства
Правдоподобная реализация
Вполне вероятно, что разумной и эффективной реализацией будет:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.my_struct2 == rhs.my_struct2 &&
lhs.an_int == rhs.an_int;
}
Обратите внимание , что это нуждается в operator==течение MyStruct2слишком.
Последствия этой реализации и альтернативы обсуждаются ниже в разделе « Обсуждение особенностей вашего MyStruct1» .
Последовательный подход к ==, <,> <= и т. Д.
std::tupleОператоры сравнения легко использовать для сравнения экземпляров ваших собственных классов - просто используйте их std::tieдля создания кортежей ссылок на поля в желаемом порядке сравнения. Обобщая мой пример отсюда :
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) ==
std::tie(rhs.my_struct2, rhs.an_int);
}
inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) <
std::tie(rhs.my_struct2, rhs.an_int);
}
// ...etc...
Когда вы «владеете» (т. Е. Можете редактировать, фактор с корпоративными и сторонними библиотеками) классом, который хотите сравнить, и особенно с готовностью C ++ 14 вывести тип возвращаемого значения функции из returnоператора, часто лучше добавить " привяжите функцию-член к классу, который вы хотите сравнивать:
auto tie() const { return std::tie(my_struct1, an_int); }
Тогда приведенные выше сравнения упрощаются до:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.tie() == rhs.tie();
}
Если вам нужен более полный набор операторов сравнения, я предлагаю операторы повышения (поиск less_than_comparable). Если по какой-то причине это не подходит, вам может понравиться идея поддержки макросов (онлайн) :
#define TIED_OP(STRUCT, OP, GET_FIELDS) \
inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
{ \
return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
}
#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
TIED_OP(STRUCT, ==, GET_FIELDS) \
TIED_OP(STRUCT, !=, GET_FIELDS) \
TIED_OP(STRUCT, <, GET_FIELDS) \
TIED_OP(STRUCT, <=, GET_FIELDS) \
TIED_OP(STRUCT, >=, GET_FIELDS) \
TIED_OP(STRUCT, >, GET_FIELDS)
... это можно использовать а-ля ...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(Версия для членов C ++ 14 здесь )
Обсуждение особенностей вашего MyStruct1
Есть последствия для выбора предоставить отдельного члена или члена operator==()...
Отдельно стоящая реализация
Вам нужно принять интересное решение. Поскольку ваш класс может быть неявно построен из a MyStruct2, автономная bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)функция / функция , не являющаяся членом, будет поддерживать ...
my_MyStruct2 == my_MyStruct1
... сначала создать временный MyStruct1с my_myStruct2, а затем выполнить сравнение. Это определенно оставит MyStruct1::an_intустановленным значение параметра конструктора по умолчанию, равное -1. В зависимости от того, включают ли an_intсравнение в реализации Вашего operator==, в MyStruct1может или не может сравнивать равно а , MyStruct2что сам сравнивает равен MyStruct1«s my_struct_2члена! Кроме того, создание временного элемента MyStruct1может быть очень неэффективной операцией, поскольку оно включает в себя копирование существующего my_struct2элемента во временный только для того, чтобы выбросить его после сравнения. (Конечно, вы можете предотвратить это неявное построение MyStruct1s для сравнения, создав этот конструктор explicitили удалив значение по умолчанию для an_int.)
Реализация члена
Если вы хотите избежать неявного построения a MyStruct1из a MyStruct2, сделайте оператор сравнения функцией-членом:
struct MyStruct1
{
...
bool operator==(const MyStruct1& rhs) const
{
return tie() == rhs.tie(); // or another approach as above
}
};
Обратите внимание на constключевое слово - необходимое только для реализации члена - сообщает компилятору, что сравнение объектов не изменяет их, поэтому его можно разрешить для constобъектов.
Сравнение видимых представлений
Иногда самый простой способ получить желаемое сравнение - это ...
return lhs.to_string() == rhs.to_string();
... которые часто тоже очень дороги - их stringмучительно создают, чтобы просто выбросить! Для типов со значениями с плавающей запятой сравнение видимых представлений означает, что количество отображаемых цифр определяет допуск, в пределах которого почти равные значения рассматриваются как равные во время сравнения.
structs на равенство? И если вам нужен простой способ,memcmpваши структуры всегда не содержат указателей.