13.1 – Введение в перегрузку операторов

Добавлено 18 июля 2021 в 01:44

В уроке «8.9 – Введение в перегрузку функций» вы узнали о перегрузке функций, которая предоставляет механизм для создания и разрешения вызовов для нескольких функций с одним и тем же именем, если каждая из этих функций имеет уникальный прототип. Это позволяет вам создавать разные вариации одной функции для работы с разными типами данных, без необходимости придумывать уникальное имя для каждого варианта.

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

В данной главе мы рассмотрим темы, связанные с перегрузкой операторов.

Операторы как функции

Рассмотрим следующий пример:

int x = 2;
int y = 3;
std::cout << x + y << '\n';

Компилятор поставляется со встроенной версией оператора плюс (+) для целочисленных операндов – эта функция складывает целые числа x и y и возвращает целочисленный результат. Когда вы видите выражение x + y, вы можете мысленно перевести его в вызов функции operator+(x, y) (где operator+ – это имя функции).

Теперь рассмотрим похожий фрагмент:

double z = 2.0;
double w = 3.0;
std::cout << w + z << '\n';

Компилятор также имеет встроенную версию оператора плюса (+) для операндов double. Выражение w + z становится вызовом функции operator+(w, z), и перегрузка функций используется для определения того, что компилятор должен вызывать версию этой функции для double вместо версии для int.

Теперь посмотрим, что произойдет, если мы попытаемся сложить два объекта пользовательского класса:

Mystring string1 = "Hello, ";
Mystring string2 = "World!";
std::cout << string1 + string2 << '\n';

Что вы ожидаете в этом случае? Интуитивно ожидаемый результат – на экране будет напечатана строка "Hello, World!". Однако, поскольку Mystring является пользовательским классом, компилятор не имеет встроенной версии оператора плюс, который он может использовать для операндов Mystring. Поэтому в этом случае он выдаст нам ошибку. Чтобы заставить этот код работать так, как мы хотим, нам нужно написать перегруженную функцию, чтобы сообщить компилятору, как оператор + должен работать с двумя операндами типа Mystring. В следующем уроке мы рассмотрим, как это сделать.

Разрешение вызовов перегруженных операторов

При вычислении выражения, содержащего оператор, компилятор использует следующие правила:

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

Какие есть ограничения на перегрузку операторов?

Во-первых, почти любой существующий оператор в C++ может быть перегружен. Исключениями являются: условный оператор (?:), sizeof, оператор разрешения области видимости (::), оператор выбора члена (.), оператор выбора указателя на член (.*), typeid и операторы приведения типов.

Во-вторых, вы можете перегрузить только существующие операторы. Вы не можете создавать новые операторы или переименовывать существующие операторы. Например, вы не можете создать оператор ** для возведения в степень.

В-третьих, по крайней мере, один из операндов в перегруженном операторе должен быть пользовательского типа. Это означает, что вы не можете перегрузить оператор плюс для работы с одним числом int и одним числом double. Однако вы можете перегрузить оператор плюс для работы с числом int и Mystring.

В-четвертых, невозможно изменить количество операндов, поддерживаемых оператором.

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

Некоторые начинающие программисты пытаются перегрузить побитовый оператор исключающее ИЛИ (^) для возведения в степень. Однако в C++ оператор ^ имеет более низкий приоритет, чем основные арифметические операторы, что приводит к неправильному вычислению выражений.

В математике возведение в степень вычисляется до базовой арифметики, поэтому 4 + 3 ^ 2 вычисляется как 4 + (3 ^ 2) => 4 + 9 => 13.

Однако в C++ арифметические операторы имеют более высокий приоритет, чем оператор ^, поэтому 4 + 3 ^ 2 вычисляется как (4 + 3) ^ 2 => 7 ^ 2 => 49.

Чтобы всё работало правильно, вам будет нужно каждый раз явно заключать в скобки фрагмент возведения в степень (например, 4 + (3 ^ 2)), что не является интуитивно понятным и потенциально подвержено ошибкам.

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

Лучшая практика


При перегрузке операторов лучше всего сохранять функции операторов как можно ближе к исходному назначению операторов.

Кроме того, поскольку у операторов нет описательных имен, не всегда понятно, для чего они предназначены. Например, оператор + может быть разумным выбором для строкового класса для объединения строк. А как насчет оператора - ? Что вы ожидаете от него? Непонятно.

Лучшая практика


Если значение оператора при применении к пользовательскому классу интуитивно непонятно, используйте вместо него именованную функцию.

При этих ограничениях вы по-прежнему найдете множество полезных функций, которые можно перегрузить для своих пользовательских классов! Вы можете перегрузить оператор +, чтобы объединять объекты пользовательского строкового класса, или складывать два объекта класса Fraction. Вы можете перегрузить оператор <<, чтобы упростить вывод вашего класса на экран (или в файл). Вы можете перегрузить оператор равенства (==), чтобы сравнить два объекта класса. Это делает перегрузку операторов одной из самых полезных функций C++ просто потому, что она позволяет вам работать с вашими классами более интуитивно понятным способом.

В следующих уроках мы более подробно рассмотрим перегрузку различных типов операторов.

Теги

C++ / CppLearnCppДля начинающихОбучениеОператор (программирование)Перегрузка (программирование)Перегрузка операторовПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.