Языки программирования используют разнообразные операторы для выполнения операций. В большинстве из них операторы имеют жесткую привязку к типам. Так, в Java сложение с оператором «+» допустимо только для целочисленных значений, чисел с плавающей запятой и строк. Если определить свои классы для математических объектов, разработчику удастся сложить их. Только для вызова соответствующего метода необходимо использовать специальные команды.

В C++ подобных ограничений нет. У данного языка программирования поддерживается так называемая перегрузка. Она возможна почти для каждого известного оператора. Это предоставляет целое поле дополнительных возможностей разработчику.

Далее предстоит познакомиться с перегрузкой операторов в C++ поближе. Необходимо не только выяснить, что это за операция такая, но и освоить принципы ее реализации. Предложенная информация окажется полезной как программистам-новичкам, так и более опытным разработчикам.

Определение

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

Перегрузка (или operator overloading) – операция, позволяющая определить для объектов классов встроенные операторы. Она широко используется разработчиками. Применяется для переопределения действий операторов языка в процессе работы с разнообразными (включая пользовательские) типами. Возможность применения встроенных операторов языка к разным типам данных.

Когда использовать процедуру

В процессе разработки программного обеспечения необходимо запомнить одно правило – перегружать операторы нужно тогда, когда соответствующая процедура имеет смысл: если он очевиден и не несет в себе никаких скрытых «сюрпризов» для программиста.

Перегруженные operators должны вести себя так же, как и их «базовые» версии. Исключения допустимы, но лишь тогда, когда они сопровождаются понятными разработчику объяснениями.

Примером подобной ситуации могут служить операторы «<<» и «>>» стандартной библиотеки C++ iostream. Они явно ведут себя не как обычный битовый сдвиг.

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

Операторная перегрузка в C++

Пример плохой перегрузки сложения – это сложение двух объектов типа «игрок» в игровом проекте. Не совсем понятно, что разработчик имел в виду для этих классов. Результат непонятен, непредсказуем. Что делает соответствующая операция – огромный вопрос. Именно поэтому использование соответствующего оператора – опасное решение.

Синтаксис

Синтаксис рассматриваемой процедуры напоминает определение функции с именем operator@, где @ – это идентификатор оператора. Вот наглядный пример реализации процесса:

Операторная перегрузка в C++

Обычно операторы (за исключением условных) будут возвращать объект или ссылку на тип, к которому относятся его аргументы.

Что нельзя перегружать

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

  • определить новый оператор нельзя (operator**);
  • нельзя перегружать тернарный оператор, доступ ко вложенным именам, доступ к полям, доступ к полям по указателю;
  • рассматриваемая процедура не допускается для операторов каста;
  • нельзя перегружать operator sizeof и operator typeid;
  • количество операндов, их ассоциативность и порядок выполнения определяется стандартной версией;
  • функция operator должна быть или нестатической (функцией-членом), или глобальной свободной функцией, или дружественной;
  • если функция дружественная, бинарный оператор имеет два аргумента, унарный – только один;
  • при работе с нестатической функцией-членом бинарный оператор имеет один аргумент, унарный – вовсе их не имеет;
  • минимум один операнд должен быть пользовательского типа, исключая operator typedef.

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

Способы реализации

Перегрузка может быть реализована в C++ несколькими способами. Всего их три:

  • через методы класса;
  • при помощи дружественных функций для класса;
  • посредством обычных функций.

Далее каждый вариант будет рассмотрен отдельно. Также предстоит изучить перегружаемые операторы C++ более подробно.

Реализация через функцию

Для использования данного приема необходимо объявить функцию, которая будет переопределять operator тела класса. Соответствующая перегрузка не может обращаться к членам класса напрямую. Воспользоваться подобной операцией можно за счет геттеров.

Операторная перегрузка в C++

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

Реализация через дружественные функции

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

Операторная перегрузка в C++

Дружественные функции могут быть определены внутри заданного класса.

Операторная перегрузка в C++

Выше можно увидеть наглядный пример реализации упомянутого метода в C++.

Через методы класса

Еще один способ операторной перегрузки в C++ – это использование методов класса.

Операторная перегрузка в C++

В этом случае у функции-метода вместо первого операнда появляется неявный параметр – указатель на объект класса. Выше – наглядный пример того, как это будет выглядеть в программном коде. Теперь можно более подробно рассмотреть операторы, для которых в C++ допустима перегрузка.

Что можно перегрузить – особенности процесса

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

В первом приведенном примере X – это пользовательский тип, для которого будет реализован operator. T – необязательный тип, пользовательский или встроенный. В качестве параметров бинарного оператора выступают lhs и rhs. Если operator объявляется в качестве метода класса, он получит префикс x::.

Operator =

Operator = – это присваивание. Его семантика:

a = b

Соответствующая запись указывает на то, что значение/состояние b будет передано a. В процессе реализации возвращается ссылка на a. За счет этого приема можно создавать цепочки вроде c = a = b.

Operator = определяется справа налево. Он правоассоциативен в отличие от большинства других операторов C++. Это значит, что a = b = c означает a = (b = c).

Типичным объявлением копирования является запись: X& X:: operator = (X const& rhs).

Перемещения, начиная с C++ 11, имеют семантику присваивания a = temporary(). Значение или состояние правой величины присваивается a за счет перемещения содержимого. На a будет возвращаться ссылка.

Ввод и вывод

Operator << может перегружаться, чтобы добиться более удобного вывода сложных структур в поток вывода.

Операторная перегрузка в C++

Выше – наглядный пример реализации соответствующей операции. аналогичным образом можно перегрузить operator >>. В этом случае вместо std:: ostream необходимо использовать std::istream.

Унарные операторы

В случае с унарными operators функция переопределения будет принимать на выход только один операнд. Operator + чаще всего ничего не делает, поэтому на практике он почти не используется. Operator – осуществляет возврат аргумента с противоположным знаком.

Операторная перегрузка в C++

Из-за того, что функция перегрузки принимает только один операнд на вход, рекомендуется осуществлять рассматриваемую операцию посредством метода класса. Выше представлен наглядный пример реализации соответствующего приема.

Метод должен быть объявлен в качестве константного. Это необходимо из-за того, что объект класса не меняется, а возвращается новый экземпляр класса.

Инкремент и декремент

Operator ++ (инкремент) и operator – (декремент) предусматривают две версии:

  • постфиксную;
  • префиксную.

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

C:\Users\ASUS\AppData\Local\Microsoft\Windows\INetCache\Content.Word\9.jpg
C:\Users\ASUS\AppData\Local\Microsoft\Windows\INetCache\Content.Word\10.jpg

Operator префиксной версии будут возвращать объект после того, как он был увеличен или уменьшен. В постфиксной интерпретации это происходит до уменьшения/увеличения. Из-за данной особенности в необходимо использовать в постфиксной версии временный объект. Он отвечает за возврат значения до его корректировки.

Соответствующее явление также приводит к тому, что при возврате невозможно использовать ссылки. Это вызвано уничтожением временного объекта при осуществлении выхода из функции. В постфиксной версии возвращаемое значение – это объект класса. Он дает меньшую эффективность.

Индексация

Еще один вариант переопределения функции в C++ – это работа с индексацией. Ниже приведен пример реализации соответствующей операции:

Операторная перегрузка в C++

Для константных объектов, когда нельзя корректировать их содержимое, допустимо использовать константную версию функции.

Операторная перегрузка в C++

При перегрузке индексации допустимо использование проверки передаваемого индекса на факт корректности. В качестве индекса может выступать целое число или любой другой тип данных: string, double и так далее.

Operator ()

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

Операторная перегрузка в C++
Операторная перегрузка в C++

Типичным примером объявления может служить запись типа: Foo X::operator() (Bar br, Baz const& bz).

Аллокация и деаллокация

Operators new new[] delete delete[] тоже поддерживают возможность переопределения. Они способны принимать любое количество аргументов. Операторы new new[] в качестве первого аргумента принимают аргумент типа sgt::size_t, а возвращать значение типа void *. Operators delete delete[] принимают первым void * и ничего не возвращать. Соответствующие операции могут быть перегружены как функции, а также для определенных классов.

Операторная перегрузка в C++

Выше можно увидеть наглядный пример реализации соответствующей операции.

Пользовательские литералы

После появления C++ 11 версии язык получил так называемые пользовательские литералы. Такие литералы будут вести себя как обычные функции. Они могут обладать квалификатором inline или constexpr. Рекомендуется начинать литерал с символа нижнего подчеркивания. Это необходимо для того, чтобы исключить коллизию с будущими стандартами.

Пользовательские литералы при перегрузке операторов могут принимать только один тип:

  • const char *;
  • unsigned long long int;
  • wchar_t;
  • char;
  • long double;
  • char16_t;
  • char32_t.

Достаточно осуществить перегрузку литерала только для типа const char *. Если подходящего кандидата система не нашла, будет произведен вызов оператора с соответствующим типом. Вот наглядный пример преобразования миль в километры при помощи изучаемого процесса:

Операторная перегрузка в C++

При перегрузке операторов строковые литералы принимают вторым аргументом std::size_t, а первым, один из:

  • const char32_t;
  • const wchar_t;
  • const char16_t;
  • const char *.

Строковые литералы применяются к записям, которые оформлены двойными кавычками.

В C++ имеется встроенный префиксный строковый литерал R. Он воспринимает все символы, написанные в кавычках, как обычные. R не интерпретирует отдельно взятые последовательности в качестве специальных символов. В качестве примера можно взять команду std::cout << R’’(Hello!\n)’’. Она выведет на экран запись Hello!\n.

Чтобы лучше знать C++, рекомендуется пройти дистанционные компьютерные курсы.