Во многих случаях при перегрузке операторов с помощью функций-"друзей"
нет никакого преимущества по сравнению с использованием функций-членов класса. Однако возможна ситуация (когда нужно, чтобы слева от бинарного оператора стоял объект встроенного типа), в которой функция-"друг" оказывается чрезвычайно полезной. Чтобы понять это, рассмотрим следующее. Как вы знаете, указатель на объект, который вызывает операторную функцию-член, передается с помощью ключевого слова this. При использовании бинарного оператора функцию вызывает объект, расположенный слева от него. И это замечательно при условии, что левый объект определяет заданную операцию. Например, предположим, что у нас есть некоторый объект ob, для которого определена операция сложения с целочисленным значением, тогда следующая запись представляет собой вполне допустимое выражение.
ob + 10; // будет работать
Поскольку объект ob
стоит слева от оператора "+", он вызывает перегруженную операторную функцию, которая (предположительно) способна выполнить операцию сложения целочисленного значения с некоторым элементом объекта ob. Но эта инструкция работать не будет.
10 + ob; // не будет работать
Дело в том, что в этой инструкции объект, расположенный слева от оператора "+"
, представляет собой целое число, т.е. значение встроенного типа, для которого не определена ни одна операция, включающая целое число и объект классового типа.Решение описанной проблемы состоит в перегрузке оператора "+"
с использованием двух функций-"друзей" В этом случае операторной функции явным образом передаются оба аргумента, и она вызывается подобно любой другой перегруженной функции, т.е. на основе типов ее аргументов. Одна версия операторной функции operator+() будет обрабатывать аргументы объект + int-значение, а другая — аргументы int-значение + объект. Перегрузка оператора "+" (или любого другого бинарного оператора) с использованием функций-"друзей" позволяет ставить значение встроенного типа как справа, так и слева от оператора. Реализация этого решения показана в следующей программе.
#include
using namespace std;
class CL {
public:
int count;
CL operator=(CL obj);
friend CL operator+(CL ob, int i);
friend CL operator+(int i, CL ob);
};
CL CL::operator=(CL obj)
{
count = obj.count;
return *this;
}
// Эта версия обрабатывает аргументы
// объект + int-значение.
CL operator+(CL ob, int i)
{
CL temp;
temp.count = ob.count + i;
return temp;
}
// Эта версия обрабатывает аргументы
// int-значение + объект.
CL operator+(int i, CL ob)
{
CL temp;
temp.count = ob.count + i;
return temp;
}
int main()
{
CL o;
o.count = 10;
cout << o.count << " "; // выводит число 10
o=10+o; // сложение числа с объектом
cout << o.count << " "; // выводит число 20
o=o+12; // сложение объекта с числом
cout << 0.count; // выводит число 32
return 0;
}
Как видите, операторная функция operator+()
перегружается дважды, позволяя тем самым предусмотреть два возможных способа участия целого числа и объекта типа CL в операции сложения.Использование функций-"друзей" для перегрузки унарных операторов
С помощью функций-"друзей"
можно перегружать и унарные операторы. Но это потребует от программиста дополнительных усилий. Для начала мысленно вернемся к исходной версии перегруженного оператора "++", определенной для класса three_d и реализованной в виде функции-члена. Для удобства приведем код этой операторной функции здесь.
// Перегрузка префиксной формы оператора "++".
three_d three_d::operator++()
{
х++;
у++;
z++;
return *this;
}
Как вы знаете, каждая функция-член получает (в качестве неявно переданного) аргумент this
, который является указателем на объект, вызвавший эту функцию. При перегрузке унарного оператора с помощью функции-члена аргументы явным образом не передаются вообще. Единственным аргументом, необходимым в этой ситуации, является неявный указатель на вызывающий объект. Любые изменения, вносимые в данные объекта, повлияют на объект, для которого была вызвана эта операторная функция. Следовательно, при выполнении инструкции х++ (в предыдущей функции) будет инкрементирован член х вызывающего объекта.