Оператор == не обнаружен при сравнении структур в C ++


96

Сравнивая два экземпляра следующей структуры, я получаю сообщение об ошибке:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

Ошибка:

ошибка C2678: двоичный '==': не найден оператор, который принимает левый операнд типа 'myproj :: MyStruct1' (или нет приемлемого преобразования)

Зачем?

Ответы:


126

В C ++ structоператоры сравнения не генерируются по умолчанию. Вам нужно написать свой:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}

21
@Jonathan: Почему C ++ должен знать, как вы хотите сравнить свои structs на равенство? И если вам нужен простой способ, memcmpваши структуры всегда не содержат указателей.
Xeo

12
@Xeo: memcmpне работает с не-членами POD (например, std::string) и заполненными структурами.
fredoverflow

16
@Jonathan "Современные" языки, которые я знаю, предоставляют ==оператор --- с семантикой, которая почти никогда не бывает той, которая нужна. (И они не предоставляют средств для его переопределения, поэтому вам придется использовать функцию-член). «Современные» языки, которые я знаю, также не предоставляют семантику значений, поэтому вы вынуждены использовать указатели, даже если они не подходят.
Джеймс Канце

4
@Jonathan Случаи определенно различаются даже в рамках одной программы. Для объектов-сущностей решение, предоставляемое Java, работает очень хорошо (и, конечно же, вы можете сделать то же самое в C ++ - это даже идиоматический C ++ для объектов-сущностей). Вопрос в том, что делать с объектами-ценностями. C ++ предоставляет значение по умолчанию operator=(даже если он часто делает неправильные вещи) по причинам совместимости с C. Однако для совместимости с C не требуется operator==. Во всем мире я предпочитаю то, что делает C ++, а не Java. (Я не знаю C #, так что, может быть, так лучше.)
Джеймс Канце

9
По крайней мере, это должно быть возможно = default!
user362515

94

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


На самом деле для операторов сравнения <,>, <=,> = нужно только реализовать <. Остальное следует, и нет никакого осмысленного способа их реализации, означающего что-либо иное, чем реализация, которая может быть сгенерирована автоматически. Странно, что вы должны реализовать их все самостоятельно.
Андре

@ Андре: чаще вручную написано int cmp(x, y)или compareфункция возвращает отрицательное значение x < y, 0 для равенства и положительное значение x > yиспользуется в качестве основы для <, >, <=, >=, ==, и !=; CRTP очень просто использовать для внедрения всех этих операторов в класс. Я уверен, что опубликовал реализацию в старом ответе, но не смог быстро ее найти.
Тони Делрой

@TonyD Конечно , вы можете сделать это, но это так же легко осуществить >, <=и >=с точки зрения <. Вы могли бы также осуществить ==и !=тот путь, но это, как правило , не может быть очень эффективной реализации , я думаю. Было бы неплохо, если бы для всего этого не потребовались CRTP или другие уловки, но стандарт просто требовал бы автогенерации этих операторов, если бы они не были явно определены пользователем и <определены.
Андре

@ Андре: это потому , что ==и !=не может быть эффективно выражается с помощью , <что использование сравнения для всего общие. «Было бы хорошо , если бы не CRTP или другие приемов , не будет нужны» - возможно, но тогда CRTP может быть легко использован для создания множества других операторов (например , побитовые |, &, ^с |=, &=и ^=, + - * / %от их форм присвоения, двоичный -от одноместный отрицании и +) - так много потенциально полезных вариаций этой темы, что простое предоставление языковой функции для одного довольно произвольного фрагмента не особенно элегантно.
Тони Делрой

Не могли бы вы добавить в правдоподобную реализацию версию, которая используется std::tieдля сравнения нескольких членов?
NathanOliver

17

Вам нужно явно определить operator ==для MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Теперь сравнение == допустимо для двух таких объектов.


11

Начиная с C ++ 20, должна быть возможность добавить в класс полный набор операторов сравнения по умолчанию ( ==, <=и т. Д.), Объявив трехсторонний оператор сравнения по умолчанию ( оператор «космический корабль»), например:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

С совместимым компилятором C ++ 20 добавления этой строки в MyStruct1 и MyStruct2 может быть достаточно, чтобы позволить сравнения на равенство, при условии, что определение MyStruct2 совместимо.



2

По умолчанию у структур нет ==оператора. Вам нужно будет написать свою собственную реализацию:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }

0

По умолчанию оператор == работает только с примитивами. Чтобы заставить ваш код работать, вам нужно перегрузить оператор == для вашей структуры.


0

Потому что вы не написали оператор сравнения для своей структуры. Компилятор не генерирует его для вас, поэтому, если вы хотите сравнить, вы должны написать его самостоятельно.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.