Перегруженный оператор определяется исключительно для операндов типа класса или перечисления и может быть объявлен только как член класса или пространства имен, принимая хотя бы один параметр типа класса или перечисления (переданный по значению или по ссылке).
Предопределенные приоритеты операторов (см. раздел 4.13) изменить нельзя. Независимо от типа класса и реализации оператора в инструкции
x == y + z;
всегда сначала выполняется operator+, а затем operator==; однако помощью скобок порядок можно изменить.
Предопределенная арность операторов также должна быть сохранена. К примеру, унарный логический оператор НЕ нельзя определить как бинарный оператор для двух объектов класса String. Следующая реализация некорректна и приведет к ошибке компиляции:
// некорректно: ! - это унарный оператор
bool operator!( const String &s1, const String &s2 )
{
return ( strcmp( s1.c_str(), s2.c_str() ) != 0 );
}
Для встроенных типов четыре предопределенных оператора ("+", "-", "*" и "&") используются либо как унарные, либо как бинарные. В любом из этих качеств они могут быть перегружены.
Для всех перегруженных операторов, за исключением operator(), недопустимы аргументы по умолчанию.
15.1.3. Разработка перегруженных операторов
Операторы присваивания, взятия адреса и оператор “запятая” имеют предопределенный смысл, если операндами являются объекты типа класса. Но их можно и перегружать. Семантика всех остальных операторов, когда они применяются к таким операндам, должна быть явно задана разработчиком. Выбор предоставляемых операторов зависит от ожидаемого использования класса.
Начинать следует с определения его открытого интерфейса. Набор открытых функций-членов формируется с учетом операций, которые класс должен предоставлять пользователям. Затем принимается решение, какие функции стоит реализовать в виде перегруженных операторов.
* После определения открытого интерфейса класса проверьте, есть ли логическое соответствие между операциями и операторами: isEmpty() становится оператором “ЛОГИЧЕСКОЕ НЕ”, operator!().
* isEqual() становится оператором равенства, operator==().
* copy() становится оператором присваивания, operator=().
У каждого оператора есть некоторая естественная семантика. Так, бинарный + всегда ассоциируется со сложением, а его отображение на аналогичную операцию с классом может оказаться удобной и краткой нотацией. Например, для матричного типа сложение двух матриц является вполне подходящим расширением бинарного плюса.
Примером неправильного использования перегрузки операторов является определение operator+() как операции вычитания, что бессмысленно: не согласующаяся с интуицией семантика опасна.
Такой оператор одинаково хорошо поддерживает несколько различных интерпретаций. Безупречно четкое и обоснованное объяснение того, что делает operator+(), вряд ли устроит пользователей класса String, полагающих, что он служит для конкатенации строк. Если семантика перегруженного оператора неочевидна, то лучше его не предоставлять.
Эквивалентность семантики составного оператора и соответствующей последовательности простых операторов для встроенных типов (например, эквивалентность оператора +, за которым следует =, и составного оператора +=) должна быть явно поддержана и для класса. Предположим, для String определены как operator+(), так и operator=() для поддержки операций конкатенации и почленного копирования:
String s1( "C");
String s2( "++" );
s1 = s1 + s2; // s1 == "C++"
Но этого недостаточно для поддержки составного оператора присваивания
s1 += s2;
Его следует определить явно, так, чтобы он поддерживал ожидаемую семантику.
Упражнение 15.1
Почему при выполнении следующего сравнения не вызывается перегруженный оператор operator==(const String&, const String&):
"cobble" == "stone"
Упражнение 15.2
Напишите перегруженные операторы неравенства, которые могут быть использованы в таких сравнениях:
String != String
String != С-строка
C-строка != String
Объясните, почему вы решили реализовать один или несколько операторов.
Упражнение 15.3
Выявите те функции-члены класса Screen, реализованного в главе 13 (разделы 13.3, 13.4 и 13.6), которые можно перегружать.
Упражнение 15.4
Объясните, почему перегруженные операторы ввода и вывода, определенные для класса String из раздела 3.15, объявлены как глобальные функции, а не функции-члены.
Упражнение 15.5
Реализуйте перегруженные операторы ввода и вывода для класса Screen из главы
13. 15.2. Друзья
Рассмотрим еще раз перегруженные операторы равенства для класса String, определенные в области видимости пространства имен. Оператор равенства для двух объектов String выглядит следующим образом: