Перегрузка операторов в C++. Способы применения
66К открытий67К показов
В прошлой части мы рассмотрели основные аспекты использования перегрузки операторов. В этом материалы вашему вниманию будут представлены перегружаемые операторы 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[]
Эти операторы полностью отличаются от всех вышеупомянутых, поскольку они не работают с пользовательскими типами. Их перегрузка весьма сложна, и поэтому не будет здесь рассматриваться.
Заключение
Основной мыслью является следующее: не стоит перегружать операторы только потому, что вы умеете это делать. Перегружайте их лишь в тех случаях, когда это выглядит естественным и необходимым. Но помните, что если вы перегрузите один оператор, то придётся перегружать и другие.
66К открытий67К показов