13.3 – Перегрузка операторов, используя обычные функции
В предыдущем уроке мы перегружали operator+
как дружественную функцию:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents{ cents }
{}
// складываем Cents + Cents с помощью дружественной функции
friend Cents operator+(const Cents &c1, const Cents &c2);
int getCents() const { return m_cents; }
};
// обратите внимание: эта функция не является функцией-членом!
Cents operator+(const Cents &c1, const Cents &c2)
{
// используем конструктор Cents и operator+(int, int)
// мы можем получить доступ к m_cents напрямую,
// потому что это дружественная функция
return { c1.m_cents + c2.m_cents };
}
int main()
{
Cents cents1{ 6 };
Cents cents2{ 8 };
Cents centsSum{ cents1 + cents2 };
std::cout << "I have " << centsSum.getCents() << " cents.\n";
return 0;
}
Использование дружественной функции для перегрузки оператора удобно, поскольку она дает вам прямой доступ к внутренним членам классов, над которыми вы работаете. В исходном примере Cents
выше наша версия дружественной функции operator+
напрямую обращалась к переменной-члену m_cents
.
Однако, если вам не нужен этот доступ, вы можете писать свои перегруженные операторы как обычные функции. Обратите внимание, что приведенный выше класс Cents
содержит функцию доступа (getCents()
), которая позволяет нам получать m_cents
без прямого доступа к закрытым членам. Поэтому мы можем переписать наш перегруженный operator+
как не-друга:
#include <iostream>
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents{ cents }
{}
int getCents() const { return m_cents; }
};
// обратите внимание: эта функция не является дружественной функцией!
Cents operator+(const Cents &c1, const Cents &c2)
{
// используем конструктор Cents и operator+(int, int)
// здесь нам не нужен прямой доступ к закрытым членам
return Cents{ c1.getCents() + c2.getCents() };
}
int main()
{
Cents cents1{ 6 };
Cents cents2{ 8 };
Cents centsSum{ cents1 + cents2 };
std::cout << "I have " << centsSum.getCents() << " cents.\n";
return 0;
}
Поскольку обычные и дружественные функции работают почти одинаково (просто у них разные уровни доступа к закрытым членам), мы, как правило, не будем различать их. Единственное отличие состоит в том, что объявление дружественой функции внутри класса также служит прототипом. В версии с обычной функцией вам нужно будет предоставить свой собственный прототип функции.
Cents.h:
class Cents
{
private:
int m_cents{};
public:
Cents(int cents)
: m_cents{ cents }
{}
int getCents() const { return m_cents; }
};
// Необходимо явно предоставить прототип для operator+,
// чтобы использование operator+ в других файлах знало,
// что эта перегрузка существует
Cents operator+(const Cents &c1, const Cents &c2);
Cents.cpp:
#include "Cents.h"
// обратите внимание: эта функция не является ни функцией-членом,
// ни дружественной функцией!
Cents operator+(const Cents &c1, const Cents &c2)
{
// используем конструктор Cents и operator+(int, int)
// здесь нам не нужен прямой доступ к закрытым членам
return { c1.getCents() + c2.getCents() };
}
main.cpp:
#include "Cents.h"
#include <iostream>
int main()
{
Cents cents1{ 6 };
Cents cents2{ 8 };
// без прототипа в Cents.h это не удалось бы скомпилировать
Cents centsSum{ cents1 + cents2 };
std::cout << "I have " << centsSum.getCents() << " cents.\n";
return 0;
}
В общем, обычная функция должна быть предпочтительнее дружественной функции, если ее можно реализовать с помощью существующих функций-членов доступа (чем меньше функций касается внутренних компонентов ваших классов, тем лучше). Однако не добавляйте дополнительные функции доступа только для того, чтобы перегрузить оператор как обычную функцию вместо дружественной функции!
Правило
Предпочитайте перегрузку операторов в виде обычных, а не дружественных функций, если это возможно без добавления дополнительных функций доступа.