Почему компиляторы C++ не определяют operator == и operator! =?

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

  • Конструктор по умолчанию (пустой)
  • Конструктор копирования
  • Деструктор
  • Оператор присваивания (operator=)

Но, похоже, он не может дать вам никаких операторов сравнения, таких как operator== или operator!=. Например:

class foo
{
public:
    std::string str_;
    int n_;
};

foo f1;        // Works
foo f2(f1);    // Works
foo f3;
f3 = f2;       // Works

if (f3 == f2)  // Fails
{ }

if (f3 != f2)  // Fails
{ }

Есть ли для этого веская причина? Почему выполнение сравнения по каждому члену может быть проблемой? Очевидно, что если класс выделяет память, вы должны быть осторожны, но для простого класса компилятор мог бы сделать это за вас?

Конечно, деструктор предоставляется бесплатно.

Johann Gerell 20.10.2008 21:28

В одном из своих недавних выступлений Алекс Степанов указал, что было ошибкой не иметь автоматического назначения по умолчанию ==, точно так же, как есть автоматическое назначение по умолчанию (=) при определенных условиях. (Аргумент об указателях непоследователен, потому что логика применима как к =, так и к ==, а не только ко второму).

alfC 02.09.2015 09:45

@becko, это одна из первых в серии «Эффективное программирование с компонентами» или «Программирование разговоров» на A9, доступных на Youtube.

alfC 16.03.2016 03:28

См. Этот ответ для информации о C++ 20: stackoverflow.com/a/50345359

vll 02.11.2018 10:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
313
4
93 401
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Я согласен, для классов типа POD компилятор мог бы сделать это за вас. Однако то, что вы считаете простым, компилятор может ошибаться. Так что лучше пусть это сделает программист.

Однажды у меня был случай POD, в котором два поля были уникальными, поэтому сравнение никогда не считалось верным. Однако сравнение, которое мне нужно, когда-либо сравнивалось только по полезной нагрузке - то, что компилятор никогда не поймет или когда-либо сможет выяснить самостоятельно.

К тому же - они пишут недолго, не так ли ?!

Ответ принят как подходящий

Компилятор не узнает, нужно ли вам сравнение указателей или глубокое (внутреннее) сравнение.

Безопаснее просто не реализовывать это и позволить программисту сделать это самим. Затем они могут делать любые предположения, которые им нравятся.

Эта проблема не мешает ему генерировать копию ctor, где это довольно вредно.

MSalters 20.10.2008 13:57

Конструкторы копирования используются в совершенно другом контексте, чем операторы сравнения. И, IMHO, его контекст понятен в том, что он делает.

spoulson 20.10.2008 14:02

Существует проблема совместимости с C: C89 создает код для структур, который имитирует присваивание C++ (и, возможно, конструкторы копирования ... я должен проверить). Таким образом, обычно C++ генерирует похожие коды.

paercebal 20.10.2008 17:43

Конструкторы копирования (и operator=) обычно работают в том же контексте, что и операторы сравнения, то есть ожидается, что после выполнения a = ba == b верен. Компилятору определенно имеет смысл предоставить operator== по умолчанию, используя ту же семантику агрегированного значения, что и для operator=. Я подозреваю, что paercebal действительно прав в том, что operator= (и copy ctor) предоставляются исключительно для совместимости с C, и они не хотели ухудшать ситуацию.

Pavel Minaev 29.10.2009 10:31

-1. Конечно, вам нужно глубокое сравнение, если бы программист хотел сравнение указателя, он бы написал (& f1 == & f2)

Viktor Sehr 01.09.2010 13:07

Виктор, предлагаю переосмыслить свой ответ. Если класс Foo содержит Bar *, то как компилятор узнает, хочет ли Foo :: operator == сравнить адрес Bar * или содержимое Bar?

Mark Ingram 01.09.2010 17:59

@Mark: если он содержит указатель, сравнение значений указателя является разумным - если он содержит значение, сравнение значений разумно. В исключительных случаях программист может отменить. Это похоже на то, как в языке реализовано сравнение между целыми числами и указателями на целые числа.

Eamon Nerbonne 26.09.2011 12:45

-1, как указывалось другими, это непоследовательный аргумент по сравнению с operator =

naumcho 05.01.2012 23:50

Ни одна из указанных причин не является действительной. Если бы в стандарте C++ говорилось, что компиляторы должны предоставлять по умолчанию оператор ==, сравнивающий каждое значение поля, это было бы не самым безумным. И, черт возьми, если нам нужна защита от указателей, просто выдайте предупреждение или ошибку в таких случаях.

Jem 27.04.2013 00:31

@MarkIngram: ваше замечание не имеет смысла. компилятор, реализованный оператором ==, должен просто вызывать операторы == для всех членов, если они доступны. так, например, clone_ptr не нарушит глубокую цепочку. и std :: vector тоже. однако указатели и интеллектуальные указатели будут сравниваться неглубоко.

v.oddou 09.04.2014 18:58

Компилятору не нужно в этом разбираться. Один из двух вариантов может быть указан в качестве стандарта, тогда поставщики компилятора просто должны будут следовать этому стандарту. Очевидным выбором, конечно же, было бы неглубокое сравнение указателей и сравнение значений для членов, точно так, как описывает v.oddou.

Balthazar 15.05.2015 18:55

Почему бы не применить ту же логику и для оператора присваивания? Возникла проблема несогласованности.

Jolly Roger 07.09.2019 15:43

@JollyRoger, не совсем, оператор присваивания может просто назначать каждую переменную-член, двусмысленности нет.

Mark Ingram 09.09.2019 12:29

Я не понимаю, насколько сложно сравнивать ценность каждого члена по отдельности. Если бы пользователь хотел сравнить адреса каждого объекта, он бы реализовал &A == &B и четко об этом сообщил. Что касается глубоких сравнений, таких как strcmp, они не вызывают проблемы, потому что C++ довольно четко понимает, что указатели являются целочисленными значениями (адресами), которые можно нормально сравнивать (==) с другим указателем.

Lapys 21.04.2020 19:38

Это несоответствие сохраняется и в стандартном комитете. В C++ 20 они изменили свое мнение. Теперь вы можете запросить оператор сравнения по умолчанию (при условии строгого порядка).

E_g 02.11.2020 15:53

C++ 0x имеет предлагал функции по умолчанию, так что вы могли бы сказать default operator==; Мы узнали, что это помогает сделать эти вещи явными.

Конструктор перемещения также может быть установлен по умолчанию, но я не думаю, что это относится к operator==. Какая жалость.

Pavel Minaev 29.10.2009 10:31

Концептуально определить равенство непросто. Даже для данных POD можно утверждать, что даже если поля одинаковы, но это другой объект (по другому адресу), они не обязательно равны. На самом деле это зависит от использования оператора. К сожалению, ваш компилятор не обладает экстрасенсорными способностями и не может этого сделать.

Кроме того, стандартные функции - отличный способ выстрелить себе в ногу. Описанные вами значения по умолчанию в основном предназначены для обеспечения совместимости со структурами POD. Однако они вызывают более чем достаточный хаос, когда разработчики забывают о них или о семантике реализаций по умолчанию.

Для структур POD нет двусмысленности - они должны вести себя точно так же, как и любой другой тип POD, а именно равенство значений (а не ссылочное равенство). Один int, созданный посредством копирования ctor из другого, равен тому, из которого он был создан; единственное, что логично сделать для struct с двумя полями int, - это работать точно так же.

Pavel Minaev 29.10.2009 10:33

Хорошо, но что, если структура POD имеет значение, релевантность которого зависит от другого значения. В случае, если значение не имеет значения, побитовое равенство больше не выполняется. Равенство действительно может варьироваться от одного и того же объекта (только с указателями / ссылками) до чего-либо, что несколько экономит (возможно, подкласса)

Paul de Vrieze 05.11.2009 01:01

Это правда, но в C++ есть четкое определение «значения» (это все, что копируется в конструкторе копирования по умолчанию). Равенство по умолчанию, если оно есть, определенно будет работать таким же образом: оно просто вызовет operator= для каждого поля. Это гарантирует, что любые значения POD сравниваются как значения, указатели сравниваются по ссылке, а объекты с переопределенными методами operator= сравниваются так, как задумано разработчиком класса. Я бы сказал, что концептуально является легко определить равенство. Они не сделали этого по другим причинам.

mgiuca 11.06.2012 13:09

@PavelMinaev Даже для структур POD это зависит от того, что они представляют, и являются ли они одинаковыми. Следует ли определять равенство как идентичность, эквивалентность или другой вариант. Я согласен с вами, что для многих структур POD правильным ответом является эквивалентность (одинаковые значения данных).

Paul de Vrieze 13.06.2012 16:11

@PavelMinaev: Все варианты использования универсальных средств тестирования эквивалентности, которые я могу придумать, требуют, чтобы каждый объект сравнивался эквивалентным самому себе. К сожалению, IEEE-754 запрещает операторам == и != проявлять такое поведение.

supercat 04.03.2015 23:22

@mgiuca: Учитывая struct {double v;} a,b; a.v=0.0/0.0; b=a;, что должен сообщить a==b? Если бы не нарушенное поведение == с типами с плавающей запятой, тогда a.v==b.v был бы истинным, а a==b не представлял бы проблемы. Тем не менее, хотя я вижу большую ценность в наличии стандартных средств запроса объектов на проверку эквивалентности, такая полезность будет зависеть от договорного требования, согласно которому тест ведет себя как отношение эквивалентности, чего оператор == не может сделать. со всеми типами.

supercat 04.03.2015 23:33

@supercat Почему вы считаете, что автоматический оператор == должен быть обязательным для удовлетворения рефлексивности? Я согласен, что это было бы идеально, но, учитывая, что в C++ уже есть операторы ==, которые не являются рефлексивными (как вы указываете), автоматически сгенерированный оператор == для составного типа может быть настолько хорош, насколько хорош операторы == его полей. Это не умаляет полезности автоматической генерации оператора == по умолчанию, который говорит, что «мы равны, если все наши поля равны согласно ==».

mgiuca 05.03.2015 04:57

@mgiuca: Я вижу значительную полезность универсального отношения эквивалентности, которое позволило бы использовать любой тип, который ведет себя как значение, в качестве ключа в словаре или подобной коллекции. Однако такие коллекции не могут работать с пользой без отношения гарантированно-рефлексивной эквивалентности. IMHO, лучшим решением было бы определить новый оператор, который все встроенные типы могли бы разумно реализовать, и определить некоторые новые типы указателей, которые были похожи на существующие, за исключением того, что некоторые определяли бы равенство как ссылочную эквивалентность, в то время как другие связывались бы с целевым оператор эквивалентности.

supercat 05.03.2015 07:07

@supercat: Это действительно просто аргумент для реализаций ==, чтобы они подчинялись правилам отношений эквивалентности. К сожалению, этот корабль уже отплыл, и я не думаю, что введение нового оператора, который почти точно такой же, как ==, за исключением нескольких угловых случаев, действительно так полезно. Насколько мне известно, встроенные типы все подчиняются законам эквивалентности, отличным от float и double. Конечно, определяемые пользователем типы могут определять == и нарушать эти законы, но они тоже могут это сделать с вашим новым оператором. Ничто из этого не влияет на то, полезно ли иметь автоматический == для пользовательских типов или нет.

mgiuca 05.03.2015 10:58

@mgiuca: Есть много ситуаций, когда необходимо проверить эквивалентность чисел с плавающей запятой. Если программисты, желающие проверить эквивалентность fp, должны написать что-то вроде bool equals(double x, double y) { return (x==y) || (x!=x) && (y!=y); }, такое тестирование будет неэффективным даже на оборудовании, которое позволяет проводить прямое тестирование эквивалентности. Добавление средства проверки эквивалентности, которое будет работать для типов fp, но также может быть реализовано другими типами, которые обещают, что оно представляет отношение эквивалентности, казалось бы лучше, чем добавление одного только для типов fp.

supercat 05.03.2015 18:05

@supercat Извините за медленный ответ. Да, я согласен с тем, что было бы полезно иметь надлежащий эквивалент для FP. Скорее, корень проблемы, о которой вы говорите, заключается не в том, что оператор == нарушен, а в том, что нарушено равенство для FP. == практически во всех смыслах и целях действительно представляет собой класс эквивалентности, и поэтому автоматическое расширение его до структур имеет смысл. У него просто есть один редкий крайний случай, когда он сломан. Добавление нового оператора только запутает ситуацию и практически не решит никаких проблем. («Почему есть 2 оператора равенства ?!»)

mgiuca 13.03.2015 09:14

@supercat По аналогии, вы можете привести почти тот же аргумент для оператора +, поскольку он неассоциативен для чисел с плавающей запятой; то есть (x + y) + z! = x + (y + z) из-за способа округления FP. (Возможно, это гораздо более серьезная проблема, чем ==, потому что это верно для обычных числовых значений.) Вы можете предложить добавить новый оператор сложения, который работает для всех числовых типов (даже int) и почти такой же, как +, но он ассоциативный (как-то). Но тогда вы добавили бы в язык раздувания и путаницы, не помогая на самом деле такому количеству людей.

mgiuca 13.03.2015 09:20

@mgiuca: Часто очень сильно полезно иметь вещи, которые очень похожи, за исключением крайних случаев, и ошибочные попытки избежать таких вещей приводят к ненужным сложностям. Если клиентскому коду иногда требуется, чтобы крайние случаи обрабатывались одним способом, а иногда - другим, наличие метода для каждого стиля обработки избавит клиента от большого количества кода обработки крайних случаев. Что касается вашей аналогии, нет способа определить операцию со значениями с плавающей запятой фиксированного размера для получения транзитивных результатов во всех случаях (хотя некоторые языки 1980-х годов имели лучшую семантику ...

supercat 14.03.2015 04:16

... чем сегодня в этом отношении), и поэтому тот факт, что они не делают невозможного, не должен быть сюрпризом. Однако нет фундаментальных препятствий для реализации отношения эквивалентности, которое было бы универсально применимо к любому типу значения, которое можно скопировать.

supercat 14.03.2015 04:19

Невозможно определить == по умолчанию, но вы можете определить != по умолчанию через ==, который вы обычно должны определить сами. Для этого вам необходимо сделать следующее:

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

Вы можете увидеть http://www.cplusplus.com/reference/std/utility/rel_ops/ для подробностей.

Кроме того, если вы определяете operator< , операторы для <=,>,> = могут быть выведены из него при использовании std::rel_ops.

Но вы должны быть осторожны при использовании std::rel_ops, потому что операторы сравнения могут быть выведены для типов, которых вы не ожидаете.

Более предпочтительный способ вывести связанный оператор из базового - использовать boost :: операторы.

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

Вы также можете генерировать «+» из «+ =», - из «- =» и т. д. (См. Полный список здесь)

Есть причина, по которой rel_ops устарел в C++ 20: потому что это не работает, по крайней мере, не везде и, конечно, не всегда. Нет надежного способа заставить sort_decreasing() скомпилировать. С другой стороны, Boost.Operators работает и всегда работал.

Barry 15.03.2019 23:26

Аргумент о том, что, если компилятор может предоставить конструктор копирования по умолчанию, он должен иметь возможность предоставить аналогичный по умолчанию operator==(), имеет определенный смысл. Я думаю, что причина решения не предоставлять для этого оператора значение по умолчанию, созданное компилятором, можно догадаться по тому, что Страуструп сказал о конструкторе копирования по умолчанию в «Проектировании и развитии C++» (Раздел 11.4.1 - Контроль копирования) :

I personally consider it unfortunate that copy operations are defined by default and I prohibit copying of objects of many of my classes. However, C++ inherited its default assignment and copy constructors from C, and they are frequently used.

Поэтому вместо «почему в C++ нет operator==() по умолчанию?» Должен был быть вопрос «почему в C++ есть конструктор присваивания и копирования по умолчанию?», С ответом, что эти элементы были включены Stroustrup неохотно для обратной совместимости с C (вероятно, причина большинства недостатков C++, но также, вероятно, основная причина популярности C++).

Для моих собственных целей в моей среде IDE фрагмент, который я использую для новых классов, содержит объявления для частного оператора присваивания и конструктора копирования, поэтому, когда я создаю новый класс, я не получаю операций присваивания и копирования по умолчанию - я должен явно удалить объявление этих операций из раздела private:, если я хочу, чтобы компилятор мог сгенерировать их для меня.

Хороший ответ. Я просто хотел бы отметить, что в C++ 11 вместо того, чтобы делать оператор присваивания и конструктор копирования частными, вы можете полностью удалить их следующим образом: Foo(const Foo&) = delete; // no copy constructor и Foo& Foo=(const Foo&) = delete; // no assignment operator

karadoc 05.06.2013 14:19

«Однако C++ унаследовал свои конструкторы присваивания и копирования по умолчанию от C». Это не означает, почему вы должны создавать ВСЕ типы C++ таким образом. Они должны были просто ограничить это простыми старыми POD, только типами, которые уже есть в C, не более.

thesaint 11.08.2014 15:25

Я, конечно, могу понять, почему C++ унаследовал это поведение от struct, но я действительно хочу, чтобы он позволял class вести себя по-другому (и разумно). В процессе это также дало бы более значимую разницу между struct и class, помимо доступа по умолчанию.

jamesdlin 01.06.2018 03:42

ИМХО, "веской" причины нет. Причина, по которой так много людей согласны с этим дизайнерским решением, заключается в том, что они не научились овладевать мощью семантики, основанной на значениях. Людям нужно написать много настраиваемых конструкторов копирования, операторов сравнения и деструкторов, потому что они используют необработанные указатели в своей реализации.

При использовании соответствующих интеллектуальных указателей (например, std :: shared_ptr) конструктор копирования по умолчанию обычно подходит, и очевидная реализация гипотетического оператора сравнения по умолчанию будет такой же хорошей.

На него ответили, что C++ не сделал ==, потому что C этого не сделал, и вот почему C предоставляет только default =, но не == в первую очередь. C хотел, чтобы все было просто: C реализован = с помощью memcpy; однако == не может быть реализован memcmp из-за заполнения. Поскольку заполнение не инициализируется, memcmp сообщает, что они разные, даже если они одинаковы. Та же проблема существует для пустого класса: memcmp говорит, что они разные, потому что размер пустых классов не равен нулю. Из вышесказанного видно, что реализация == более сложна, чем реализация = в C. Некоторый код пример относительно этого. Ваше исправление приветствуется, если я ошибаюсь.

C++ не использует memcpy для operator= - это будет работать только для типов POD, но C++ предоставляет operator= по умолчанию и для типов, отличных от POD.

Flexo 07.12.2011 20:58

Да, C++ реализовал = более изощренным способом. Кажется, C просто реализовал = с простым memcpy.

Rio Wing 10.12.2011 02:34

В этом видео Алексей Степанов, создатель STL, отвечает на этот вопрос примерно в 13:00. Подводя итог, наблюдая за эволюцией C++, он утверждает, что:

  • К сожалению, == и! = не объявляются неявно (и Бьярн с ним согласен). В правильном языке эти вещи должны быть готовы для вас (далее он предполагает, что вы не должны иметь возможность определять знак равно, который нарушает семантику ==)
  • Причина, по которой это так, имеет свои корни (как и многие проблемы C++) в C. Там оператор присваивания неявно определяется с помощью побитовое присвоение, но это не сработает для ==. Более подробное объяснение можно найти в этом статья от Бьярна Страуструпа.
  • В следующем вопросе Почему тогда не использовался член по сравнению с членством он говорит, что удивительная вещь: C был своего рода родным языком, и парень, реализующий эти вещи для Ричи, сказал ему, что он обнаружил, что это сложно реализовать!

Затем он говорит, что в (отдаленном) будущем == и знак равно будут генерироваться неявно.

Даже в C++ 20 компилятор по-прежнему не будет неявно генерировать operator== для вас.

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

Но вы получите возможность явно по умолчанию ==начиная с C++ 20:

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

== по умолчанию делает == поэлементно (точно так же, как конструктор копирования по умолчанию выполняет поэлементное построение копии). Новые правила также обеспечивают ожидаемую взаимосвязь между == и !=. Например, с объявлением выше я могу написать и то, и другое:

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

Эта особенность (по умолчанию operator== и симметрия между == и !=) происходит от одно предложение, который был частью более широкой языковой функции, то есть operator<=>.

@ dcmm88 К сожалению, он не будет доступен в C++ 17. Я обновил ответ.

Anton Savin 20.04.2017 02:57

Однако модифицированное предложение, которое допускает то же самое (кроме краткой формы), будет в C++ 20 :)

Rakete1111 02.03.2019 09:28

@artin Это имеет смысл, поскольку добавление новых функций в язык не должно нарушать существующую реализацию. Добавление новых стандартов библиотеки или новых возможностей компилятора - это одно. Добавление новых функций-членов туда, где их раньше не было, - это совсем другая история. Чтобы обезопасить свой проект от ошибок, потребуется гораздо больше усилий. Я лично предпочел бы, чтобы флаг компилятора переключался между явным и неявным значением по умолчанию. Вы строите проект из более старого стандарта C++, используйте явное значение по умолчанию по флагу компилятора. Вы уже обновили компилятор, поэтому вам следует правильно его настроить. Для новых проектов сделайте это неявным.

Maciej Załucki 06.01.2020 23:55

Is there a good reason for this? Why would performing a member-by-member comparison be a problem?

Функционально это может не быть проблемой, но с точки зрения производительности сравнение отдельных элементов по умолчанию может быть более неоптимальным, чем присвоение / копирование отдельных элементов по умолчанию. В отличие от порядка присваивания, порядок сравнения влияет на производительность, поскольку первый неравный член подразумевает, что остальные могут быть пропущены. Поэтому, если есть некоторые члены, которые обычно равны, вы хотите сравнить их последними, и компилятор не знает, какие члены с большей вероятностью будут равны.

Рассмотрим этот пример, где verboseDescription - длинная строка, выбранная из относительно небольшого набора возможных описаний погоды.

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(Конечно, компилятор будет иметь право игнорировать порядок сравнений, если он признает, что у них нет побочных эффектов, но, по-видимому, он все равно будет брать свою очередь из исходного кода, где у него нет лучшей собственной информации.)

Но никто не мешает вам написать оптимизирующее сравнение, определяемое пользователем, если вы обнаружите проблему с производительностью. По моему опыту, это будет незначительное количество случаев.

Peter - Reinstate Monica 26.03.2019 18:36

C++ 20 позволяет легко реализовать оператор сравнения по умолчанию.

Пример из cppreference.com:

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>

Я удивлен, что они использовали Point в качестве примера для операции заказ, поскольку не существует разумного способа по умолчанию упорядочить две точки с координатами x и y ...

pipe 14.11.2018 12:51

@pipe Если вам все равно, в каком порядке расположены элементы, имеет смысл использовать оператор по умолчанию. Например, вы можете использовать std::set, чтобы убедиться, что все точки уникальны, а std::set использует только operator<.

vll 24.01.2019 14:50

О типе возврата auto: для этот случай всегда можно предположить, что это будет std::strong_ordering от #include <compare>?

kevinarpe 14.06.2020 16:09

@kevinarpe Тип возврата - std::common_comparison_category_t, который для этого класса становится порядком по умолчанию (std::strong_ordering).

vll 15.06.2020 08:44

Просто чтобы ответы на этот вопрос оставались полными с течением времени: начиная с C++ 20, он может быть автоматически сгенерирован с помощью команды auto operator<=>(const foo&) const = default;

Он сгенерирует все операторы: ==,! =, <, <=,> И> =, подробности см. В https://en.cppreference.com/w/cpp/language/default_comparisons.

Из-за операторского вида <=> его называют космическим оператором. Также см. Зачем нам нужен оператор космического корабля <=> в C++?.

Обновлено: также в C++ 11 довольно изящная замена, доступная с std::tie, см. https://en.cppreference.com/w/cpp/utility/tuple/tie для полного примера кода с bool operator<(…). Интересная часть, измененная для работы с ==:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

std::tie работает со всеми операторами сравнения и полностью оптимизируется компилятором.

Другие вопросы по теме