Перегрузка оператора: функция-член или функция-не-член?

121

Я читал, что перегруженный оператор, объявленный как функция-член, является асимметричным, потому что он может иметь только один параметр, а другой параметр, передаваемый автоматически, является thisуказателем. Так что не существует стандарта для их сравнения. С другой стороны, перегруженный оператор, объявленный как a, friendявляется симметричным, потому что мы передаем два аргумента одного и того же типа и, следовательно, их можно сравнивать.

У меня вопрос: когда я все еще могу сравнивать lvalue указателя со ссылкой, почему предпочтение отдается друзьям? (использование асимметричной версии дает те же результаты, что и симметричная) Почему алгоритмы STL используют только симметричные версии?

Badmaash
источник
11
Ваш вопрос действительно касается только бинарных операторов. Не все перегруженные операторы ограничены одним параметром. Оператор () может принимать любое количество параметров. С другой стороны, унарные операторы не могут иметь никаких параметров.
Чарльз Сальвия
3
stackoverflow.com/a/4421729/103167
Бен Войгт,
4
Это одна из многих тем, рассмотренных в FAQ
Бен Фойгт,

Ответы:

148

Если вы определяете свою перегруженную оператором функцию как функцию-член, компилятор переводит такие выражения, как s1 + s2в s1.operator+(s2). Это означает, что перегруженная оператором функция-член вызывается для первого операнда. Так работают функции-члены!

Но что, если первый операнд не является классом? Большая проблема возникает, если мы хотим перегрузить оператор, первый операнд которого, скорее, не является типом класса double. Итак, вы не можете так писать 10.0 + s2. Однако вы можете написать перегруженную оператором функцию-член для таких выражений, как s1 + 10.0.

Чтобы решить эту проблему с упорядочением , мы определяем перегруженную функцию оператора как friendЕСЛИ ей нужен доступ к privateчленам. Делайте это friendТОЛЬКО, когда ему нужен доступ к закрытым членам. В противном случае просто сделайте это функцией, не являющейся другом, чтобы улучшить инкапсуляцию!

class Sample
{
 public:
    Sample operator + (const Sample& op2); //works with s1 + s2
    Sample operator + (double op2); //works with s1 + 10.0

   //Make it `friend` only when it needs to access private members. 
   //Otherwise simply make it **non-friend non-member** function.
    friend Sample operator + (double op1, const Sample& op2); //works with 10.0 + s2
}

Прочтите это:
Небольшая проблема с упорядочением в операндах
Как функции, не являющиеся членами, улучшают инкапсуляцию

Наваз
источник
2
«Делайте это friendтолько тогда, когда ему нужен доступ к закрытым членам ... и когда у вас нет / вам надоело писать аксессоры, не так ли?
badmaash
4
@Abhi: Выберите свой выбор: улучшенная инкапсуляция против привычки ленивого письма!
Nawaz
6
@matthias, не все операторы коммутативны. Простой пример a/b.
edA-qa mort-ora-y
3
Распространенный способ избежать необходимости требовать от операторов, не являющихся членами, friend- реализовать их в терминах операторов присваивания операции (которые почти наверняка будут открытыми членами). Например, вы можете определить T T::operator+=(const T &rhs)как член, а затем определить не член T operator(T lhs, const T &rhs)как return lhs += rhs;. Функция, не являющаяся членом, должна быть определена в том же пространстве имен, что и класс.
Адриан Маккарти
2
@ricky: Но если lhs является копией (как в моем комментарии), то факт изменения lhs не имеет значения.
Адриан Маккарти
20

Это не обязательно различие между friendперегрузками операторов и перегрузками операторов функций-членов, как между перегрузками глобальных операторов и перегрузками операторов функций-членов.

Одна из причин, по которой следует предпочесть перегрузку глобального оператора, заключается в том, что вы хотите разрешить выражения, в которых тип класса отображается справа от двоичного оператора. Например:

Foo f = 100;
int x = 10;
cout << x + f;

Это работает, только если есть глобальная перегрузка оператора для

Оператор Foo + (int x, const Foo & f);

Обратите внимание, что перегрузка глобального оператора не обязательно должна быть friendфункцией. Это необходимо только в том случае, если ему нужен доступ к закрытым членам Foo, но это не всегда так.

В любом случае, если бы была Fooтолько перегрузка оператора функции-члена, например:

class Foo
{
  ...
  Foo operator + (int x);
  ...
};

... тогда мы сможем иметь только выражения, в которых Fooэкземпляр появляется слева от оператора плюса.

Чарльз Сальвия
источник
3
+1 для различения функций-членов и функций, не являющихся членами, а не функций-членов и друзей. Думаю, сегодня мы бы сказали «глобальная область или область пространства имен».
Адриан Маккарти,