Перегрузка операторов в C++. Способы применения

63462

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

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

На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator=

  • Определение справа налево: в отличие от большинства операторов, operator= правоассоциативен, т.е. a = b = c означает a = (b = c).

Копирование

  • Семантика: присваивание a = b. Значение или состояние b передаётся a. Кроме того, возвращается ссылка на a. Это позволяет создавать цепочки вида c = a = b.
  • Типичное объявление: X& X::operator= (X const& rhs). Возможны другие типы аргументов, но используется это нечасто.
  • Типичная реализация:X& X::operator= (X const& rhs) {  if (this != &rhs) {    //perform element wise copy, or:    X tmp(rhs); //copy constructor    swap(tmp);  }  return *this;}

Перемещение (начиная с C++11)

  • Семантика: присваивание a = temporary(). Значение или состояние правой величины присваивается a путём перемещения содержимого. Возвращается ссылка на a.
  • Типичные объявление и реализация:X& X::operator= (X&& rhs) {  //take the guts from rhs  return *this;}
  • Сгенерированный компилятором operator=: компилятор может создать только два вида этого оператора. Если же оператор не объявлен в классе, компилятор пытается создать публичные операторы копирования и перемещения. Начиная с  C++11 компилятор может создавать оператор по умолчанию:X& X::operator= (X const& rhs) = default;Сгенерированный оператор просто копирует/перемещает указанный элемент, если такая операция разрешена.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator+, -, *, /, %

  • Семантика: операции сложения, вычитания, умножения, деления, деления с остатком. Возвращается новый объект с результирующим значением.
  • Типичные объявление и реализация:X operator+ (X const lhs, X const rhs) {  X tmp(lhs);  tmp += rhs;  return tmp;}Обычно, если существует operator+, имеет смысл также перегрузить и operator+= для того, чтобы использовать запись a += b вместо a = a + b. Если же operator+= не перегружен, реализация будет выглядеть примерно так:X operator+ (X const& lhs, X const& rhs) {  // create a new object that represents the sum of lhs and rhs:  return lhs.plus(rhs);}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

Унарные operator+, –

  • Семантика: положительный или отрицательный знак. operator+ обычно ничего не делает и поэтому почти не используется. operator- возвращает аргумент с противоположным знаком.
  • Типичные объявление и реализация:X X::operator- () const {  return /* a negative copy of *this */;  } X X::operator+ () const {  return *this;}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator<<, >>

  • Семантика: во встроенных типах операторы используются для битового сдвига левого аргумента. Перегрузка этих операторов с именно такой семантикой встречается редко, на ум приходит лишь std::bitset. Однако, для работы с потоками была введена новая семантика, и перегрузка операторов ввода/вывода весьма распространена.
  • Типичные объявление и реализация: поскольку в стандартные классы iostream добавлять методы нельзя, операторы сдвига для определённых вами классов нужно перегружать в виде свободных функций:ostream& operator<< (ostream& os, X const& x) {  os << /* the formatted data of rhs you want to print */;  return os;} istream& operator>> (istream& is, X& x) {  SomeData sd;  SomeMoreData smd;  if (is >> sd >> smd) {    rhs.setSomeData(sd);    rhs.setSomeMoreData(smd);  }  return lhs;}Кроме того, тип левого операнда может быть любым классом, которые должен вести себя как объект ввода/вывода, то есть правый операнд может быть и встроенного типа.MyIO& MyIO::operator<< (int rhs) {  doYourThingWith(rhs);  return *this;}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

Бинарные operator&, |, ^

  • Семантика: Битовые операции “и”, “или”, “исключающее или”. Эти операторы перегружаются очень редко. Опять же, единственным примером является std::bitset.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator+=, -=, *=, /=, %=

  • Семантика: a += b обычно означает то же, что и a = a + b. Поведение остальных операторов аналогично.
  • Типичные определение и реализация: поскольку операция изменяет левый операнд, скрытое приведение типов нежелательно. Поэтому эти операторы должны быть перегружены как методы класса.X& X::operator+= (X const& rhs) {  //apply changes to *this  return *this;}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator&=, |=, ^=, <<=, >>=

  • Семантика: аналогична operator+=, но для логических операций. Эти операторы перегружаются так же редко, как и operator| и т.д. operator<<= и operator>>= не используются для операций ввода/вывода, поскольку operator<< и operator>> уже изменяют левый аргумент.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator==, !=

  • Семантика: проверка на равенство/неравенство. Смысл равенства очень сильно зависит от класса. В любом случае, учитывайте следующие свойства равенств:Рефлексивность, т.е. a == a.Симметричность, т.е. если a == b , то b == a.Транзитивность, т.е. если a == b и b == c, то a == c.
  • Типичные объявление и реализация:bool operator== (X const& lhs, X cosnt& rhs) {  return /* check for whatever means equality */} bool operator!= (X const& lhs, X const& rhs) {  return !(lhs == rhs);}Вторая реализация operator!= позволяет избежать повторов кода и исключает любую возможную неопределённость в отношении любых двух объектов.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator<, <=, >, >=

  • Семантика: проверка на соотношение (больше, меньше и т.д.). Обычно используется, если порядок элементов однозначно определён, то есть сложные объекты с несколькими характеристиками сравнивать бессмысленно.
  • Типичные объявление и реализация:bool operator< (X const& lhs, X const& rhs) {  return /* compare whatever defines the order */} bool operator> (X const& lhs, X const& rhs) {  return rhs < lhs;}Реализация operator> с использованием operator< или наоборот обеспечивает однозначное определение. operator<= может быть реализован по-разному, в зависимости от ситуации. В частности, при отношении строго порядка operator== можно реализовать лишь через operator<:bool operator== (X const& lhs, X const& rhs) {  return !(lhs < rhs) && !(rhs < lhs);}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator++, —

  • Семантика: a++ (постинкремент) увеличивает значение на 1 и возвращает старое значение. ++a (преинкремент) возвращает новое значение. С декрементом operator-- все аналогично.
  • Типичные объявление и реализация:X& X::operator++() { //preincrement  /* somehow increment, e.g. *this += 1*/;  return *this;} X X::operator++(int) { //postincrement  X oldValue(*this);  ++(*this);  return oldValue;}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator()

  • Семантика: исполнение объекта-функции (функтора). Обычно используется не для изменения объекта, а для использования его в качестве функции.
  • Нет ограничений на параметры: в отличие от прошлых операторов, в этом случае нет никаких ограничений на количество и тип параметров. Оператор может быть перегружен только как метод класса.
  • Пример объявления:Foo X::operator() (Bar br, Baz const& bz);
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator[]

  • Семантика: доступ к элементам массива или контейнера, например, в std::vector, std::map, std::array.
  • Объявление: тип параметра может быть любым. Тип возвращаемого значения обычно является ссылкой на то, что хранится в контейнере. Часто оператор перегружается в двух версиях, константной и неконстантной:Element_t& X::operator[](Index_t const& index); const Element_t& X::operator[](Index_t const& index) const;
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator!

  • Семантика: отрицание в логическом смысле.
  • Типичные объявление и реализация:bool X::operator!() const {  return !/*some evaluation of *this*/;}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

explicit operator bool

  • Семантика: использования в логическом контексте. Чаще всего используется с умными указателями.
  • Реализация:explicit X::operator bool() const {  return /* if this is true or false */;}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator&&, ||

  • Семантика: логические “и”, “или”. Эти операторы определены только для встроенного логического типа и работают по “ленивому” принципу, то есть второй аргумент рассматривается, только если первый не определяет результат. При перегрузке это свойство теряется, поэтому перегружают эти операторы редко.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

Унарный operator*

  • Семантика: разыменовывание указателя. Обычно перегружается для классов с умными указателями и итераторами. Возвращает ссылку на то, куда указывает объект.
  • Типичные объявление и реализация:T& X::operator*() const {  return *_ptr;}
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator->

  • Семантика: доступ к полю по указателю. Как и предыдущий, этот оператор перегружается для использования с умными указателями и итераторами. Если в коде встречается оператор ->, компилятор перенаправляет вызовы на operator->, если возвращается результат пользовательского типа.
  • Usual implementation:T* X::operator->() const { return _ptr; }
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator->*

  • Семантика: доступ к указателю-на-поле по указателю. Оператор берёт указатель на поле и применяет его к тому, на что указывает *this, то есть objPtr->*memPtr — это то же самое, что и (*objPtr).*memPtr. Используется очень редко.
  • Возможная реализация:template T& X::operator->*(T V::* memptr){  return (operator*()).*memptr;}Здесь X — это умный указатель, V — тип, на который указывает X, а T — тип, на который указывает указатель-на-поле. Неудивительно, что этот оператор редко перегружают.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

Унарный operator&

  • Семантика: адресный оператор. Этот оператор перегружают очень редко.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator,

  • Семантика: встроенный оператор “запятая”, применённый к двум выражениям, выполняет их оба в порядке записи и возвращает значение второго из них. Перегружать его не рекомендуется.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator~

  • Семантика: оператор побитовой инверсии. Один из наиболее редко используемых операторов.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

Операторы приведения типов

  • Семантика: позволяет скрытое или явное приведение объектов класса к другим типам.
  • Объявление://conversion to T, explicit or implicitX::operator T() const;   //explicit conversion to U const&explicit X::operator U const&() const; //conversion to V&V& X::operator V&();Эти объявления выглядят странно, поскольку в них отсутствует тип возвращаемого значения. Он является частью имени оператора у не указывается дважды. Стоит помнить, что большое количество скрытых приведений может повлечь за собой непредвиденные ошибки в работе программы.
На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

operator new, new[], delete, delete[]

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

На данный момент этот блок не поддерживается, но мы не забыли о нём!Наша команда уже занята его разработкой, он будет доступен в ближайшее время.

Заключение

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

63462