Друг против функций-членов в Перегрузке Оператора C++
Ранее я узнал о перегрузке операторов в C++ как функций-членов, а также функций-друзей класса. Хотя я знаю, как перегрузить операторы в C++, используя оба метода. Но я все еще смущен, что ** какой лучше **? Функция-член или функция-друг для перегрузки операторов, какую из них использовать и почему? Пожалуйста, ведите меня! Ваш ответ будет высоко оценен. Я буду рад и благодарен за ваши ответы.
4 ответа
Выбор не "участник или друг", но "участник или не участник".
(Дружба часто используется слишком часто, и в школах она преподается слишком рано.)
Это потому, что вы всегда можете добавить открытую функцию-член, которую может вызывать свободная функция.
Например:
class A
{
public:
explicit A(int y) : x(y) {}
A plus(const A& y) const { return A{x + y.x}; }
private:
int x;
};
A operator+(const A& lhs, const A& rhs) { return lhs.plus(rhs); }
Что касается того, как выбрать: если оператор не принимает экземпляр класса в качестве своего левого операнда, он должен быть свободной функцией, в противном случае это в значительной степени вопрос личного вкуса (или стандартов кодирования, если вы не в одиночестве).
Пример:
// Can't be a member because the int is on the left.
A operator+ (int x, const A& a) { return A{x} + a; }
Для операторов, которые имеют соответствующий оператор мутирования (например, +
а также +=
), оператор мутирования обычно является членом, а другой - не членом:
class B
{
public:
explicit B(int y) : x(y) {}
B& operator+= (const B& y) { x += y.x; return *this; }
private:
int x;
};
B operator+(B lhs, const B& rhs) { return lhs += rhs; }
но, конечно, это тоже можно разобрать:
class C
{
public:
explicit C(int y) : x(y) {}
C& add(const C& y) { x += y.x; return *this; }
private:
int x;
};
C& operator+=(C& lhs, const C& rhs) { return lhs.add(rhs); }
C operator+(C lhs, const C& rhs) { return lhs += rhs; }
Функции-члены и друг не являются двумя противоположностями, среди которых вам нужно выбрать только одну. Они оба могут быть использованы, например, чтобы избежать дублирования кода путем реализации одного, а другой вызовет его.
struct A
{
A operator+=(A const & second);
};
A operator+(A const &first, A const &second)
{
A temp(first);
temp += second;
return temp;
}
Но в случае, если вы хотите выбрать одну функцию, если она должна быть членом или нет, вот правило, которому я следую: реализовать функцию, не являющуюся членом (friend
или нет), когда у меня есть бинарный оператор, чтобы уважать симметрию, которую мы ожидаем. Пример: с A
и возможность конвертировать int
в A
неявно (с конструктором, принимающим int
), если у меня есть функция-член, то каков будет результат.
A a1, a2;
a1 + a2; // OK
a1 + 42; // OK
42 + a2; // KO
С бесплатной функцией, есть результат.
A a1, a2;
a1 + a2; // Ok
a1 + 42; // Ok
42 + a2; // Ok
Конкретный класс C++, использующий эту возможность: std::string
,
#include <iostream>
#include <string>
int main()
{
std::string s {"World"};
// Works with absolutely no problem.
std::string chaine = "Hello " + s;
std::cout << chaine << std::endl;
}
В заключение, вот хорошая ссылка на SO, чтобы помочь вам.
Вероятно, нет фиксированного правила для этого, поэтому я просто высказываю свое мнение: по возможности используйте функцию-член для перегрузки операторов. Основная причина этого заключается в том, что я вижу операторы как неотъемлемую часть класса, а логика класса более локализована.
Причиной предпочтения свободных функций для перегрузки операторов было бы поддержание класса как можно более скудным (используйте Friend только при необходимости).
В некоторых случаях операторы не могут быть членами, поэтому в этом случае нет обсуждения. В основном функции-члены имеют экземпляр класса в качестве первого параметра, что не всегда возможно (например, оператор<< для ввода-вывода).
Не далеко от вкуса, но некоторые случаи использования явно требуют одного из двух способов.
Когда оператор принимает в качестве аргументов только члены класса, который вы проектируете, инкапсуляция голосует за использование функции-члена. Пример: добавление двух объектов:
class A {
...
A operator + (const class A& other); // naturally a member function
...
};
Напротив, когда член класса, который вы пишете, является вторым аргументом оператора, вы можете использовать только функцию друга
std::outstream& operator << (std::outstream& out, const class A& a);
class A {
...
friend std::outstream& operator << (std::outstream& out, const class A& a); // must be friend here
};
Самое большее, он может быть членом std::outstream
но A
класс не существовал, когда std::outstream
класс был создан в стандартной библиотеке C++...