Не знайдено оператора == під час порівняння структур у 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 ++ structs не мають оператора порівняння, згенерованого за замовчуванням. Вам потрібно написати своє:

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

21
@ Джонатан: Чому C ++ знає, як ти хочеш порівняти свої structs для рівності? І якщо ви хочете простий спосіб, завжди memcmpтак довго ваші структури не містять покажчика.
Xeo,

12
@Xeo: memcmpне працює з членами, що не є POD (наприклад, std::string), і з підбитими структурами.
fredoverflow

16
@Jonathan «Сучасні» мови, які я знаю, надають ==оператору --- семантику, яка майже ніколи не потрібна. (І вони не надають засобів перевизначення цього, тож у підсумку вам доведеться використовувати функцію-член). «Сучасні» мови, які я знаю, також не забезпечують семантики цінностей, тому ви змушені використовувати покажчики, навіть коли вони не підходять.
James Kanze,

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

9
Принаймні це має бути можливо = default!
user362515

93

C ++ 20 представив порівняння за замовчуванням, вони ж "космічний корабель"operator<=> , що дозволяє запитувати згенеровані компілятором </ <=/ ==/ !=/ >=/ та / або >оператори з очевидною / наївною (?) Реалізацією ...

auto operator<=>(const MyClass&) const = default;

... але ви можете налаштувати це для більш складних ситуацій (обговорено нижче). Дивіться тут мовну пропозицію, яка містить обґрунтування та обговорення. Ця відповідь залишається актуальною для C ++ 17 та попередніх версій, а також для розуміння того, коли слід налаштовувати реалізаціюoperator<=> ....

Може здатися трохи корисним для C ++, якщо він ще не стандартизував це раніше, але часто структури / класи мають деякі члени даних, які слід виключити із порівняння (наприклад, лічильники, кешовані результати, ємність контейнера, код успіху / помилки останньої операції, курсори), як а також рішення щодо безлічі речей, включаючи, але не обмежуючись ними:

  • які поля потрібно порівняти спочатку, наприклад, порівняння певного intчлена може дуже швидко усунути 99% нерівних об’єктів, тоді як map<string,string>член може часто мати однакові записи та бути порівняно дорогим для порівняння - якщо значення завантажуються під час виконання, програміст може мати уявлення про компілятор не може
  • при порівнянні рядків: чутливість до регістру, еквівалентність пробілів та роздільників, уникнення конвенцій ...
  • точність при порівнянні плаваючих / подвійних
  • чи слід вважати рівними значення NaN з плаваючою комою
  • порівняння покажчиків або вказівників на дані (і якщо останні, як дізнатись, чи є покажчики масивами та скільки об’єктів / байтів потребують порівняння)
  • чи має значення порядок при порівнянні невідсортованих контейнерів (наприклад vector,list ), і якщо так, чи нормально сортувати їх на місці перед порівнянням порівняно з використанням додаткової пам'яті для сортування часописів кожного разу, коли проводиться порівняння
  • скільки елементів масиву на даний момент містять допустимі значення, які слід порівнювати (чи є десь розмір чи сторожовий?)
  • який член a 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==()...

Автономна реалізація

Вам потрібно прийняти цікаве рішення. Оскільки ваш клас можна неявно побудувати з 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' 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" */ }
};

Тепер порівняння == дозволено для 2 таких об’єктів.


11

Починаючи з C ++ 20, має бути можливо додати повний набір операторів порівняння за замовчуванням ( ==, <=тощо) до класу, оголосивши тристоронній оператор порівняння за замовчуванням ( оператор "космічного корабля"), наприклад:

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

За допомогою сумісного компілятора C ++ 20 додавання цього рядка до MyStruct1 та MyStruct2 може бути достатнім для порівняння рівності за умови, що визначення MyStruct2 сумісне.


2

Порівняння не працює на структурах на C або C ++. Натомість порівняйте за полями.


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.