2008-10-20 6 views
227

Я большой поклонник позволить компилятору сделать как можно больше работы для вас. При написании простого класса компилятор может дать вам следующее за «бесплатно»:Почему компиляторы 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 
{ } 

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

+3

Конечно же, деструктор предоставляется бесплатно. – 2008-10-20 17:28:27

ответ

62

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

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

+210

Эта проблема не мешает ей создавать копию ctor, где это довольно вредно. – MSalters 2008-10-20 09:57:09

+1

Конструкторы копирования используются в совершенно другом контексте, чем операторы сравнения. И, ИМХО, его контекст ясен в том, что он делает. – spoulson 2008-10-20 10:02:25

+11

Существует проблема совместимости с C: C89 создает код для структур, который имитирует назначение C++ (и, вероятно, копирует конструкторы ... я должен проверить). Таким образом, это нормально, C++ генерирует аналогичные коды. – paercebal 2008-10-20 13:43:50

1

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

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

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

10

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

+3

Я думал, что только «специальные функции-члены» (конструктор по умолчанию, конструктор копирования, оператор присваивания и деструктор) могут быть явно дефолтными. Разве они распространили это на некоторых других операторов? – 2008-10-21 22:57:19

+4

Переместить конструктор также можно по умолчанию, но я не думаю, что это относится к `operator ==`. Какая жалость. – 2009-10-29 07:31:48

7

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

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

+8

Нет никакой двусмысленности для структур POD - они должны вести себя точно так же, как это делает любой другой тип POD, который является равенством ценности (а не ссылкой). Один «int», созданный посредством копии ctor из другого, равен тому, из которого он был создан; единственная логическая задача, которую нужно сделать для `struct` из двух` int` полей, - работать точно так же. – 2009-10-29 07:33:09

+0

Ну, но что, если структура POD имеет значение, релевантность которого зависит от другого значения. В случае, если это значение не имеет значения, оно больше не является битовым равенством. Равенство действительно может варьироваться от одного и того же объекта (только с указателями/ссылками) к чему-либо, что-то вроде сохранения (возможно, подкласса) – 2009-11-04 22:01:05

-2

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

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

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

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

+1

Унаследовано от boost :: noncopyable - еще один очень хороший способ подавления конструкторов коротких копий и операторов присваивания. – CesarB 2008-10-20 10:18:51

+0

Да, это правда.Фактически, это настолько распространено, что оно превратило его в стимул и регулярно используется. – janm 2008-10-20 10:56:51

15

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

#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::operators.

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

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

264

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

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

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

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

8

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

  • оператор новый оператор
  • новый []
  • оператор удаления
  • оператор удалить []
37

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

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

30

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

16

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

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

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

46

ОБНОВЛЕНИЕ 2: К сожалению, это предложение didn't make it to C++17, поэтому на этом языке пока ничего не меняется.

ОБНОВЛЕНИЕ: Текущая версия предложения, которая имеет очень высокий шанс проголосовать за C++ 17, - here.

Существует recent proposal (N4126) по явно дефолтным операторам сравнения, который имеет очень положительные отзывы от стандартного комитета, поэтому, надеюсь, мы увидим его в некоторой форме на C++ 17.

Короче говоря, предлагаемый синтаксис:

struct Thing 
{ 
    int a, b, c; 
    std::string d; 
}; 

bool operator==(const Thing &, const Thing &)= default; 
bool operator!=(const Thing &, const Thing &)= default; 

Или в friend форме для классов с частными полями:

class Thing 
{ 
    int a, b; 

    friend bool operator<(Thing, Thing) = default; 
    friend bool operator>(Thing, Thing) = default; 
    friend bool operator<=(Thing, Thing) = default; 
    friend bool operator>=(Thing, Thing) = default; 
}; 

Или даже в краткой форме:

struct Thing 
{ 
    int a, b, c; 
    std::string d; 

    default: ==, !=, <, >, <=, >=; // defines the six non-member functions 
}; 

Из Конечно, все это может измениться к моменту принятия этого предложения.

0

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

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

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

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