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


121

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

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


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


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 для различения функций-членов и функций, не являющихся членами, а не функций-членов и друзей. Думаю, сегодня мы бы сказали «глобальная область или область пространства имен».
Адриан Маккарти,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.