Перевантаження оператора: функція члена проти функції, яка не є членом?


121

Я читав, що перевантажений оператор, оголошений як функція-член, є асиметричним, оскільки він може мати лише один параметр, а інший параметр, переданий автоматично, - це thisпокажчик. Тож не існує стандартів для їх порівняння. З іншого боку, перевантажений оператор, оголошений як a, friendє симетричним, оскільки ми передаємо два аргументи одного типу і, отже, їх можна порівняти.

Моє запитання полягає в тому, що коли я ще можу порівняти значення значення вказівника з посиланням, чому друзі віддають перевагу? (використання асиметричної версії дає ті ж результати, що і симетричні) Чому алгоритми STL використовують лише симетричні версії?


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


4
Це одна з багатьох тем, що висвітлюються в FAQ
Ben Voigt,

Відповіді:


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: Оберіть свій вибір: Покращена інкапсуляція проти лінивої звички писати!
Наваз

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екземпляр з’являється зліва від оператора plus.


3
+1 для розмежування між функціями члена та не членами, а не функціями членів та друзів. Я думаю, сьогодні ми б сказали "глобальна область чи область імен".
Адріан Маккарті
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.