Перегрузка оператора ‹и оператора› в одном классе

В моей домашней работе я должен разработать сообщение класса; среди других атрибутов он имеет атрибут «приоритет» (основная цель - реализация очереди с приоритетами).

Как и в контейнере, я должен проверить, больше ли один объект, чем другой, у меня перегружен оператор '>'. Теперь у меня есть несколько общих вопросов по этому поводу ...

Вопрос первый:

Если я перегрузил оператор '>', должен ли я перегрузить оператор '‹' для аргументов (const Message &, const Message &)?

Я считаю, что перегрузка обоих> и ‹и их использование в коде вызовет ошибку:

if(message1 > message2)
   { ... }

(В следующем коде вызывается оператор> для объекта message1 или оператор ‹объекта message2?)

Но что, если я использую такой оператор:

if(message1 < message2)
   { ... }

?

оператор> объявлен как функция друга:

friend bool operator>(const Message& m1, const Message& m2)

Должен ли он быть объявлен как функция-член?

Спасибо.


person Nemanja Boric    schedule 07.11.2009    source источник


Ответы (3)


Если я перегружаю оператор '>', должен ли я перегружать оператор '‹' для аргументов (const Message &, const Message &)?

да. Фактически, в большинстве кодов принято предпочесть использование <, а не > (не спрашивайте меня, почему, вероятно, исторически). Но в более общем плане всегда перегружайте полный набор связанных операторов; в вашем случае это, вероятно, также будет ==, !=, <= и >=.

(В следующем коде вызывается оператор> для объекта message1 или оператор ‹объекта message2?)

Он всегда вызывает то, что находит в коде. Для компилятора C ++ нет абсолютно никакой связи между > и <. Для нас они выглядят одинаково, но компилятор видит два совершенно разных, не связанных друг с другом символа. Таким образом, нет двусмысленности: компилятор вызывает то, что видит.

Должен ли он быть объявлен как функция-член?

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

Чтобы понять это, рассмотрим следующий случай:

struct RealFraction {
    RealFraction(int x) { this.num = x; this.den = 1; }
    RealFraction(int num, int den) { normalize(num, den); }
    // Rest of code omitted.

    bool operator <(RealFraction const& rhs) {
        return num * rhs.den < den * rhs.num;
    }
};

Теперь вы можете написать следующее сравнение:

int x = 1;
RealFraction y = 2;
if (y < x) …

но вы не можете написать следующее:

if (x < y) …

хотя существует неявное преобразование из int в RealFraction (с использованием первого конструктора).

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

person Konrad Rudolph    schedule 07.11.2009
comment
Спасибо, я где-то читал, что если я перегружу одну операцию отношения, кажется рациональным перегружать их все. - person Nemanja Boric; 07.11.2009
comment
И спасибо за ответ друга / члена. Я понимаю что ты имеешь ввиду :). - person Nemanja Boric; 07.11.2009
comment
Этот ответ также должен указывать на то, что большинство операторов отношения можно описать с помощью нескольких операторов, обычно < и ==. Например, operator>= можно записать: bool operator>=(const T& l, const T& r) { return !(l < r); } И действительно, они должны быть записаны таким образом. - person greyfade; 10.09.2013

Да, вам следует ... но вы можете (и, возможно, должны) реализовать три из <, >, <=, >= с точки зрения другого. Это гарантирует их постоянное поведение. Обычно < - это тот, который реализован в других терминах, потому что это оператор по умолчанию, используемый в sets и maps.

Например. если вы реализовали <, вы могли бы определить >, <= и >= вот так.

inline bool operator>(const Message& lhs, const Message& rhs)
{
    return rhs < lhs;
}

inline bool operator<=(const Message& lhs, const Message& rhs)
{
    return !(rhs < lhs);
}

inline bool operator>=(const Message& lhs, const Message& rhs)
{
    return !(lhs < rhs);
}

== и != часто реализуются отдельно. Иногда классы реализуют ==, так что a == b тогда и только тогда, когда !(a < b) && !(b < a), но иногда == реализуется как более строгие отношения, чем !(a < b) && !(b < a). Однако выполнение этого приводит к большей сложности для клиента этого класса.

В некоторых ситуациях может быть приемлемо иметь <, >, <= и >=, но не == или !=.

person CB Bailey    schedule 07.11.2009
comment
Да, да, да! Я забыл об этом упомянуть, но это важно. Вы, вероятно, также должны сказать, что ваш чистый код будет столь же эффективным, как и кодирование каждого сравнения вручную и независимо. Это очень важно, потому что это означает, что любая попытка отказаться от этого совета в пользу производительности является преждевременной оптимизацией. - person Konrad Rudolph; 07.11.2009
comment
В качестве такового можно также использовать сокращения, такие как class Message: boost::less_than_comparable<Message, Message> и rel_ops в <utility> (последние пахнут несколько подозрительно, поскольку их можно использовать с помощью объявления using). С обоими operator< достаточно, и другие реализованы с точки зрения этого. - person UncleBens; 07.11.2009
comment
OTOH, попытка использовать rel_ops может быть очень болезненной. Обычно вам нужно импортировать их в другое пространство имен, и они имеют тенденцию быть слишком жадными. Если ваш класс находится в глобальном пространстве имен, вы можете получить rel_ops сравнения для всех видов неподходящих типов. - person CB Bailey; 07.11.2009
comment
Я думаю, вы бы предпочли использовать rel_ops в файлах реализации для специальных возможностей сравнения, чем постоянно предоставлять операторы сравнения для класса. - person UncleBens; 07.11.2009
comment
@UncleBens: Хорошо, я неправильно понял. Я все еще не уверен, что понимаю, что вы имеете в виду. Вы имеете в виду что-то вроде { using std::rel_ops::operator>; return lhs > rhs; } как тело оператора ›? - person CB Bailey; 07.11.2009
comment
Это действительно выглядит несколько глупо. Что ж, возможно, действительно, если ваш класс находится в своем собственном пространстве имен ... Я думал об этом один или два раза, но это заставило меня почувствовать себя грязным. - person UncleBens; 07.11.2009

Если присвоение явно не требует использования перегрузки оператора, вы также можете рассмотреть возможность использования объекта функции. Причина в том, что, вероятно, существует более одного способа сравнить два сообщения на предмет «меньше чем» (например, сравнить содержимое лексикографически, время публикации и т. Д.), И поэтому значение operator< интуитивно не понятно.

С std::priority_queue используемый объект функции указывается в качестве третьего параметра шаблона (к сожалению, вам также потребуется указать второй - базовый тип контейнера):

#include <queue>
#include <string>
#include <functional>
#include <vector>

class Message
{
    int priority;
    std::string contents;
    //...
public:
    Message(int priority, const std::string msg):
        priority(priority),
        contents(msg)
    {}
    int get_priority() const { return priority; }
    //...
};

struct ComparePriority:
    std::binary_function<Message, Message, bool> //this is just to be nice
{
    bool operator()(const Message& a, const Message& b) const
    {
        return a.get_priority() < b.get_priority();
    }
};

int main()
{
    typedef std::priority_queue<Message, std::vector<Message>, ComparePriority> MessageQueue;
    MessageQueue my_messages;
    my_messages.push(Message(10, "Come at once"));
}

При реализации собственной очереди приоритетов вы можете сделать это следующим образом:

class MessageQueue
{
    std::vector<Message> messages;
    ComparePriority compare;
    //...
    void push(const Message& msg)
    {
        //...
        if (compare(msg, messages[x])) //msg has lower priority
        //...
    }
};
person UncleBens    schedule 07.11.2009