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

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

  • Конструктор по умолчанию (пустой)
  • Конструктор копирования
  • Деструктор
  • Оператор присваивания (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
{ }

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

Ответ 1

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

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

Ответ 2

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

Я лично считаю это несчастным что операции копирования определяются по умолчанию, и я запрещаю копирование объекты многих из моих классов. Тем не менее, C++ унаследовал его по умолчанию присваивание и копирование конструкторов из С, и они часто используются.

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

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

Ответ 3

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

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

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

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

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<=>.

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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

#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, лучше, потому что он определяет использование оператора для класса, который вам нужен, а не для всех классов в области.

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

Ответ 8

С++ 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<=>

Ответ 9

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

Ответ 10

Просто заметка, предоставленная компилятором бесплатно:

  • оператор new
  • оператор new []
  • удалить оператор
  • оператор delete []

Ответ 11

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

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

Ответ 12

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

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

Рассмотрим этот пример, где 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
    }
}

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

Ответ 13

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

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

Благодаря виду оператора <=>, он называется оператором космического корабля. Также смотрите Зачем нам космический корабль & lt; = & gt; оператор в C++?.

Ответ 14

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

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

Кроме того, они не занимают много времени, чтобы написать?!

Ответ 15

Операторы сравнения по умолчанию будут корректными, но с небольшим количеством времени; Я ожидаю, что они будут источником проблем, а не чем-то полезным.

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

class NonAssignable {
// ....
private:
    NonAssignable(const NonAssignable&);  // Unimplemented
    NonAssignable& operator=(const NonAssignable&);  // Unimplemented
};

В большом количестве кода обычно встречается комментарий "конструктор копирования по умолчанию и оператор = ОК", чтобы указать, что это не ошибка, что они были удалены или явно определены.