Разработка перегруженных операторов
Операторы присваивания, взятия адреса и оператор “запятая” имеют предопределенный смысл, если операндами являются объекты типа класса. Но их можно и перегружать. Семантика всех остальных операторов, когда они применяются к таким операндам, должна быть явно задана разработчиком. Выбор предоставляемых операторов зависит от ожидаемого использования класса.
Начинать следует с определения его открытого интерфейса. Набор открытых функций-членов формируется с учетом операций, которые класс должен предоставлять пользователям. Затем принимается решение, какие функции стоит реализовать в виде перегруженных операторов.
После определения открытого интерфейса класса проверьте, есть ли логическое соответствие между операциями и операторами:
- 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.