Перегрузка как оператора <и операторa> в том же классе
В моей домашней работе мне нужно создать класс Message; среди других атрибутов он имеет атрибут "приоритет" (основная цель - реализовать очередь приоритетов).
Как и в контейнере, я должен проверить, больше ли один объект, чем другой, я перегрузил оператор ' > '. Теперь у меня есть несколько общих вопросов об этом...
Вопрос один:
Если я перегружаю оператор ' > ', я должен перегрузить оператор '<' для аргументов (const Message &, const Message &)?
Мое мнение таково, что перегрузка и > и < и использование его в коде вызовет ошибку:
if(message1 > message2)
{ ... }
(Является ли следующий код вызовом operator > для объекта message1 или объекта operator < message2?)
Но что, если я использую оператор следующим образом:
if(message1 < message2)
{ ... }
?
Оператор
объявляется как функция друга:
friend bool operator>(const Message& m1, const Message& m2)
Нужно ли его объявлять как функцию-член?
Спасибо.
Ответы
Ответ 1
Если я перегружаю оператор ' > ', я должен перегрузить оператор '<' для argumenst (const Message &, const Message &)?
Да. Фактически, его соглашение в большинстве кодов предпочитает использовать <
над >
(не спрашивайте меня, почему, вероятно, исторический). Но в целом, всегда перегружайте полный набор связанных операторов; в вашем случае это, вероятно, также будет ==
, !=
, <=
и >=
.
(Является ли следующий код вызовом operator > для объекта message1 или объекта operator < message2?)
Он всегда вызывает то, что находит в коде. Для компилятора С++ нет абсолютно никакой связи между >
и <
. Для нас они похожи, но компилятор видит два совершенно разных, несвязанных символа. Поэтому нет никакой двусмысленности: компилятор называет то, что видит.
Нужно ли его объявлять как функцию-член?
Нет. Фактически, его лучше не объявлять как функцию-член. Объявление его как функции-члена означает, что первый аргумент (т.е. Левая часть выражения) должен действительно быть объектом 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
(с использованием первого конструктора).
Если, с другой стороны, вы использовали функцию, не являющуюся членом для реализации оператора, оба сравнения будут работать, потому что С++ знал бы, чтобы вызвать неявный конструктор по первому аргументу.
Ответ 2
Да, вы должны... но вы можете (и, возможно, должны) реализовать три из <
, >
, <=
, >=
в терминах другой. Это гарантирует, что они будут вести себя последовательно. Обычно <
- это тот, который другие реализованы в терминах, поскольку он используется по умолчанию для операторов set
и map
s.
например. если вы внедрили <
, вы можете определить >
, <=
и >=
следующим образом.
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)
. Однако это приводит к большей сложности для клиента этого класса.
В некоторых ситуациях допустимо иметь <
, >
, <=
и >=
, но не ==
или !=
.
Ответ 3
Если присваивание явно не требует использования перегрузки оператора, вы также можете рассмотреть использование объекта функции. Причина в том, что существует, вероятно, более одного способа сравнить два Сообщения для "меньше" (например, сравнить содержимое лексикографически, время публикации и т.д.), И поэтому значение 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
//...
}
};