Перегрузка операторов
Содержание
Общая информация
Перегрузка операторов бывает полезна, например, в следующих случаях:
- Вместо get(i) удобнее использовать [i].
- a.plus(b) или plus(a, b) удобнее заменить на a + b.
- Вместо p.get()->print() удобнее использовать p->print().
- Обобщённое программирование.
В С++ существуют следующие операторы:
- +a -a
- + - / * %
- += -= /= *= %=
- && || !
- = == !=
- & | ~
- a-> *a &a
- >> <<
- ,
- . ::
- ()?():()
- [] ()
- a++ ++a a-- --a
- > < >= <=
- new, delete
- приведение к типу
Два последних типа операторов будут разобраны в отдельной лекции.
Из всего этого списка нельзя перегружать те операторы, которые выделены красным цветом.
Для перегрузки операторов характерны следующие особенности:
- Для того, чтобы можно было перегрузить оператор, необходимо, чтобы хотя бы один из аргументов был пользовательского типа.
- Большую часть операторов можно перегружать как внутри класса, так и снаружи, причём их не нужно определять одновременно и там, и там. Однако некоторые операторы могут быть определены только внутри класса, а именно =, a->, [], ().
- Для операторов && и || применяется логика "ленивых" вычислений. При перегрузке этих операторов она теряется.
- Оператор -> должен обязательно возвращать указатель.
- Операторов () может быть сколько угодно, с различными сигнатурами.
- Оператор , в качестве результата возвращает тип и значение самого правого выражения. Перегружается он очень редко. Пример использования:
a = f(), g();
//сначала вычисляется f(), потом g(), и a=f()
Пример перегрузки оператора бинарного сложения
Оператор бинарного сложения можно перегрузить снаружи класса следующим образом:
BigNum operator+ (BigNum const &a, BigNum const &b)
{
...
}
А так будет выглядеть определение внутри:
BigNum BigNum::operator+ (BigNum const &b) const
{
...
}
//первым аргументом в данном случае является *this
//метод не меняет значение, поэтому const
Пример перегрузки оператора +=
Определение оператора снаружи класса выглядит следующим образом:
BigNum & operator+= (BigNum &a, BigNum const &b)
{
...
return a;
}
А определение внутри класса такое:
BigNum & BigNum::operator+= (BigNum const &b) const
{
...
return *this;
}
В частности, можно передавать операнды различных типов (например, для перегрузки BigNum+int).
Другие примеры
Определение унарного минуса снаружи класса:
BigNum operator- (BigNum const &a)
{
...
}
Он же - внутри класса:
BigNum BigNum::operator- () const
{
...
}
Далее показана разница между определением методов ++a и a++:
BigNum & operator++ (BigNum &a);
BigNum & operator++ (BigNum &a, size_t);
//во втором случае используется фиктивный параметр,
//который нужен только для разделения префиксного и постфиксного инкрементов
BigNum BigNum::operator[] (size_t i);
BigNum BigNum::operator() (...);
...
BigNum b;
b(17, 34);
Примеры грамотного использования перегрузки операторов
Array
struct Array
{
int operator[] (size_t i) const
{
assert (i<size_);
return data_[i];
}
int & operator[] (size_t i)
{
return data_[i];
}
void swap (Array &a)
{
std::swap(a.data_, data_);
std::swap(a.size_, size_);
}
//swap меняет значения местами
Array & operator= (Array const &a)
{
if (this != &a)
Array(a).swap(*this);
return *this;
}
};
Можно подключить или отключить assert, это зависит от параметров компиляции. При выходе за границы массива тогда возникнет ошибка.
Для использования ostream и istream достаточно подключить
#include <iosfwd>;
т.к. iostream весьма и весьма велик.
Array a;
std::cout << a;
std::ostream &operator<< (std::ostream & os, Array const &a);
//этот и следующий операторы могут быть переопределены только снаружи класса
std::istream &operator>> (std::ostream & is, Array &a);
//при чтении объект изменяется (std::cin >> a)
Замечание: swap(Array(a)) не сработает, потому что swap принимает константную ссылку, а временные объекты нельзя передавать по константной ссылке.
BigNum
Оператор += лучше определять внутри класса, а оператор + — через += снаружи, как показано в примере:
struct BigNum
{
BigNum & operator+= (BigNum &b)
{
...
}
...
}
BigNum operator+ (BigNum a, BigNum const &b)
{
return a+=b;
}
//+, определённый снаружи, может быть вызван от аргументов,
//которые может быть приведены к типу
BigNum a(10); //должен быть BigNum(int)
a+20; //работает и внутри, и снаружи
20+a; //работает только снаружи
bool operator== (BigNum const &a, BigNum const &b)
{
...
}
bool operator!= (BigNum const &a, BigNum const &b)
{
return !(a==b);
}
Вообще говоря, достаточно определить < и == и легко получить все остальные сравнения. Например, (a<=b) выражается как (!(b<a))
Умные указатели
{
smart_pstr p = new ...
}
В данном примере рассматривается перегрузка операторов p-> и *p.
struct smart_pstr
{
string *p_;
...
string * operator-> () const
{
return p_;
}
string & operator* () const
{
return *p_;
}
};
//предположим, что мы переопределим оператор ниже:
operator== (smart_pstr, smart_pstr)
...
smart_pstr p;
string *q;
p == q; //произойдёт неявное преобразование типа,
//создастся smart_pstr(q), а потом он будет удалён
//для того, чтобы этого избежать, нужно конструктор сделать explicit
//(т.е. запретить неявное преобразование типов)