Лекция N. Перегрузка операторов

Общая информация

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

  1. Вместо get(i) удобнее использовать [i].
  2. a.plus(b) или plus(a, b) удобнее заменить на a + b.
  3. Вместо p.get()->print() удобнее использовать p->print().
  4. Обобщённое программирование.

В С++ существуют следующие операторы:

Два последних типа операторов будут разобраны в отдельной лекции.

Из всего этого списка нельзя перегружать те операторы, которые выделены красным цветом.

Для перегрузки операторов характерны следующие особенности:

Пример перегрузки оператора бинарного сложения

Оператор бинарного сложения можно перегрузить снаружи класса следующим образом:

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
//(т.е. запретить неявное преобразование типов)