Ответ 1
Компилятор не знал бы, хотите ли вы сравнить указатель или глубокое (внутреннее) сравнение.
Это безопаснее просто не реализовать его и позволить программисту сделать это сами. Затем они могут сделать все предположения, которые им нравятся.
Я большой поклонник позволить компилятору сделать как можно больше работы для вас. При написании простого класса компилятор может предоставить вам следующее для "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
{ }
Есть ли веская причина для этого? Почему выполнение сравнения по каждому члену может быть проблемой? Очевидно, если класс выделяет память, то вы хотите быть осторожным, но для простого класса наверняка компилятор может сделать это для вас?
Компилятор не знал бы, хотите ли вы сравнить указатель или глубокое (внутреннее) сравнение.
Это безопаснее просто не реализовать его и позволить программисту сделать это сами. Затем они могут сделать все предположения, которые им нравятся.
Аргумент, что если компилятор может предоставить конструктор копирования по умолчанию, он должен иметь возможность предоставить аналогичный по умолчанию operator==()
, имеет определенный смысл. Я думаю, что причина решения не предоставлять сгенерированный компилятором по умолчанию для этого оператора может быть угадана по тому, что сказал Страуструп о конструкторе копирования по умолчанию в "Проектировании и развитии C++" (Раздел 11.4.1 - Управление Копирование):
Я лично считаю это несчастным что операции копирования определяются по умолчанию, и я запрещаю копирование объекты многих из моих классов. Тем не менее, C++ унаследовал его по умолчанию присваивание и копирование конструкторов из С, и они часто используются.
Таким образом, вместо "почему C++ не имеет значение по умолчанию operator==()
?", Вопрос должен был быть "почему C++ имеет конструктор назначения и копирования по умолчанию?", С ответом, что эти элементы неохотно включались Stroustrup для обратной совместимости с C (вероятно, причина большинства бородавок C++, но также, вероятно, основная причина популярности C++).
Для моих собственных целей в моей среде IDE фрагмент кода, который я использую для новых классов, содержит объявления для частного оператора присваивания и конструктора копирования, так что при создании нового класса я не получаю операции присваивания и копирования по умолчанию - мне нужно явно удалить объявление из этих операций из раздела private:
, если я хочу, чтобы компилятор мог генерировать их для меня.
Даже в С++ 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<=>
.
ИМХО, нет "хорошей" причины. Причина, по которой так много людей согласна с этим дизайнерским решением, состоит в том, что они не научились овладевать властью семантики, основанной на значении. Людям нужно написать много конструкторов пользовательских копий, операторов сравнения и деструкторов, потому что они используют исходные указатели в своей реализации.
При использовании соответствующих интеллектуальных указателей (например, std:: shared_ptr) конструктор копии по умолчанию обычно отлично, и очевидная реализация гипотетического оператора сравнения по умолчанию будет такой же тонкой.
Он ответил, что С++ не сделал ==, потому что C не сделал, и вот почему C предоставляет только значение по умолчанию =, но no == на первом месте. C хотел, чтобы это было просто: C реализован = через memcpy; однако == не может быть реализована memcmp из-за заполнения. Поскольку заполнение не инициализировано, memcmp говорит, что они разные, хотя они одинаковы. Такая же проблема существует для пустого класса: memcmp говорит, что они разные, потому что размер пустых классов не равен нулю. Из вышеизложенного видно, что реализация == более сложна, чем реализация = в C. Некоторый код пример относительно этого. Ваша коррекция оценена, если я ошибаюсь.
В этом видео Алексей Степанов, создатель STL, решает этот вопрос примерно в 13:00. Подводя итог, наблюдая за развитием C++, он утверждает, что:
Затем он говорит, что в (далеком) будущем будут генерироваться неявно == и ! =.
Невозможно определить значение по умолчанию ==
, но вы можете определить значение по умолчанию !=
через ==
, которое вы обычно должны определить сами.
Для этого вы должны делать следующее:
#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, лучше, потому что он определяет использование оператора для класса, который вам нужен, а не для всех классов в области.
Вы также можете сгенерировать "+" из "+ =", - из "- =" и т.д. (см. полный список здесь)
С++ 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<=>
С++ 0x имеет предложение для функций по умолчанию, поэтому вы можете сказать default operator==;
Мы узнали, что это помогает сделать эти вещи явными.
Просто заметка, предоставленная компилятором бесплатно:
Концептуально определить равенство непросто. Даже для данных POD можно утверждать, что даже если поля одинаковы, но это другой объект (с другим адресом), он не обязательно равен. Это фактически зависит от использования оператора. К сожалению, ваш компилятор не психический и не может этого сделать.
Кроме того, функции по умолчанию - отличные способы стрелять в ногу. По умолчанию вы указываете на совместимость с структурами POD. Тем не менее, они вызывают более чем достаточно хаоса, когда разработчики забывают о них или семантику реализаций по умолчанию.
Есть ли веская причина для этого? Почему выполнение сопоставления по отдельности может быть проблемой?
Это не может быть проблемой функционально, но с точки зрения производительности, сравнение по умолчанию по умолчанию может быть более субоптимальным, чем назначение/копирование поэтапно по умолчанию. В отличие от порядка назначения, порядок сравнения влияет на производительность, потому что первый неравный член подразумевает, что остальные могут быть пропущены. Поэтому, если есть некоторые члены, которые обычно равны, вы хотите сравнить их последними, и компилятор не знает, какие члены с большей вероятностью будут равны.
Рассмотрим этот пример, где 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
}
}
(Конечно, компилятор будет иметь право игнорировать порядок сравнений, если он признает, что у них нет побочных эффектов, но, по-видимому, он по-прежнему будет принимать свой код из исходного кода, где он не имеет лучшей информации о его собственные.)
Просто чтобы ответы на этот вопрос оставались полными с течением времени: начиная с C++ 20 его можно автоматически генерировать с помощью команды auto operator<=>(const foo&) const = default;
Он сгенерирует все операторы: ==,! =, & Lt ;, & lt; =,> и> =, подробности см. в https://en.cppreference.com/w/cpp/language/default_comparisons.
Благодаря виду оператора <=>
, он называется оператором космического корабля. Также смотрите Зачем нам космический корабль & lt; = & gt; оператор в C++?.
Я согласен, что для классов типа POD компилятор может сделать это за вас. Однако то, что вы считаете простым компилятором, может оказаться неправильным. Поэтому лучше позволить программисту это сделать.
У меня был случай POD, когда два из полей были уникальными, поэтому сравнение никогда не будет считаться истинным. Однако сравнение, которое мне нужно было когда-либо сравнивать только с полезной нагрузкой, - то, что компилятор никогда не поймет или не сможет понять на себе.
Кроме того, они не занимают много времени, чтобы написать?!
Операторы сравнения по умолчанию будут корректными, но с небольшим количеством времени; Я ожидаю, что они будут источником проблем, а не чем-то полезным.
Кроме того, часто используемые методы по умолчанию часто нежелательны. Видя такой код, чтобы избавиться от конструктора копирования по умолчанию и оператора =, очень часто встречается:
class NonAssignable {
// ....
private:
NonAssignable(const NonAssignable&); // Unimplemented
NonAssignable& operator=(const NonAssignable&); // Unimplemented
};
В большом количестве кода обычно встречается комментарий "конструктор копирования по умолчанию и оператор = ОК", чтобы указать, что это не ошибка, что они были удалены или явно определены.