В 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
элемента во временный только для того, чтобы выбросить его после сравнения. (Конечно, вы можете предотвратить это неявное построение MyStruct1
s для сравнения, создав этот конструктор 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
мучительно создают, чтобы просто выбросить! Для типов со значениями с плавающей запятой сравнение видимых представлений означает, что количество отображаемых цифр определяет допуск, в пределах которого почти равные значения рассматриваются как равные во время сравнения.
struct
s на равенство? И если вам нужен простой способ,memcmp
ваши структуры всегда не содержат указателей.