Сравнение объектов с помощью bool operator ==

Итак, после прочтения некоторых вопросов и ответов SO, я до сих пор не понимаю, зачем использовать

friend bool operator==( BaseClass const &left, BaseClass const &right )

вместо

bool operator==( BaseClass const &right )

прямо сейчас у меня есть что-то вроде http://pastebin.com/pKsTabC0 (исправлено) - и, похоже, он работает нормально. Но, может быть, я что-то упустил? Любые предложения?

Обновление 1

Хорошо, я изменил источник, чтобы он работал правильно http://ideone.com/fIAmB. Удалены ненужные виртуальные и добавленные константы. Тем не менее я не понимаю, зачем использовать друзей...

Ответ 1

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

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

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

Также обратите внимание, что операции сравнения элементов (равенство в этом случае) обычно должны быть const.

EDIT: Кажется, вам может быть интересно только сравнение, основанное на статическом типе аргумента левой руки, что должно быть более простой проблемой. В этом случае ваш код обрабатывает все случаи, за исключением случаев, когда аргумент левой руки является неявным образом преобразуется в Base или Derived с помощью какого-либо механизма, кроме наследования (оператор преобразования или конструктор преобразования). Если вас не волнуют эти случаи, то равенство членов отлично.

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

EDIT2 (действительно быстрый обзор нечленов, не друзей):

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

bool operator==(const MyType& left, const MyType& right)
{
    return left.key() == right.key();
}

Ответ 2

Там нет ничего неправильного с функцией-членом, но свободная функция более общая. Функция-член принудительно имеет операнд левой стороны типа BaseClass или дочерний тип, тогда как свободная функция принимает любые типы, которые неявно конвертируются в BaseClass, а не только типы, которые являются частью дерева наследования.

Ответ 3

Самая большая проблема, которую я вижу с помощью virtual bool operator==( BaseClass const &right ), заключается в том, что без многократной динамической отправки * следующее утверждение не будет выполнено.

class Derived1 : public BaseClass {
    bool operator==( BaseClass const &right ) override {
        return true;
    }
}

class Derived2 : public BaseClass {
    bool operator==( BaseClass const &right ) override {
        return false;
    }
}

Derived1 d1;
Derived2 d2;
assert((d1 == d2) == (d2 == d1));

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


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

Ответ 4

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

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

Что касается реализации двоичных операторов как функций-членов... Этот вопрос уже рассматривался уже не один раз. Бинарные операторы, реализованные как функции-члены, ведут себя "асимметрично" в отношении неявных преобразований аргументов. Вот почему обычно лучше реализовать симметричные двоичные операторы как автономные функции.