Коли слід використовувати "friend" в C ++?


354

Я читав відповіді на C ++ і мені було цікаво friendдекларацію. Я особисто ніколи його не використовував, проте мені цікаво вивчити мову.

Який хороший приклад використання friend?


Читання питань FAQ трохи довше мені подобається ідея << >>перевантаження оператора та додавання їх як друга цих класів. Однак я не впевнений, як це не порушує інкапсуляцію. Коли ці винятки можуть залишатися в межах суворості, яка є OOP?


5
Хоча я згоден з відповіддю, що клас друзів - це не обов'язково Погана річ, я, як правило, трактую його як малий код. Це часто, хоча і не завжди, вказує на необхідність перегляду ієрархії класів.
Мауг каже, що повернути Моніку

1
Ви б використовували клас друзів, де вже є щільна муфта. Ось для чого він зроблений. Наприклад, таблиця бази даних та її індекси тісно пов'язані. Коли таблиця змінюється, всі її індекси повинні бути оновлені. Отже, клас DBIndex оголосив DBTable як друг, щоб DBTable міг безпосередньо отримувати доступ до внутрішніх індексів. Але публічного інтерфейсу до DBIndex не було б; не має сенсу навіть читати індекс.
shawnhcorey

"Пуристи" OOP з невеликим практичним досвідом стверджують, що друг порушує принципи ООП, оскільки клас повинен бути єдиним підтримувачем його приватної держави. Це добре, поки ви не зіткнетесь із загальною ситуацією, коли для підтримки загального приватного стану потрібно два класи.
kaalus

Відповіді:


335

По-перше (ІМО) не слухають людей, які кажуть, що friendце не корисно. Це корисно. У багатьох ситуаціях у вас з'являться об'єкти з даними або функціональними можливостями, які не призначені для загального доступу. Особливо це стосується великих кодових баз з багатьма авторами, які, можливо, лише поверхнево знайомі з різних областей.

Є альтернативи специфікатору друзів, але часто вони громіздкі (конкретні класи на рівні cpp / маскуються typedefs) або не є надійними (коментарі чи умовні назви функцій).

На відповідь;

Специфікатор friendдозволяє вказаному класу отримати доступ до захищених даних або функціональних можливостей у класі, роблячи заяву про друзів. Наприклад, у наведеному нижче коді будь-хто може запитати дитину про своє ім'я, але тільки мати та дитина можуть змінити ім'я.

Ви можете взяти цей простий приклад далі, розглянувши більш складний клас, наприклад, Window. Цілком ймовірно, що у вікні буде багато елементів функції / даних, які не повинні бути загальнодоступними, але ARE потрібні відповідному класу, наприклад, WindowManager.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

114
В якості додаткової примітки згадується FAQ C ++, що friend посилює інкапсуляцію. friendнадає селективний доступ членам, як і protectedце робить. Будь-який дрібнозернистий контроль є кращим, ніж надання доступу громадськості. Інші мови також визначають селективні механізми доступу, врахуйте C # internal. Найбільш негативна критика навколо використання friendпов'язана з більш жорстким з'єднанням, що, як правило, сприймається як погана річ. Однак у деяких випадках більш міцна муфта - це саме те, що ви хочете, і friendдає вам таку силу.
Андре Карон

5
Скажіть, будь ласка, детальніше про (бетонні класи на рівні cpp) та (маскування typedefs), Ендрю ?
ОмарОтман

18
Ця відповідь, здається, більше зосереджена на поясненні того, що friendє, а не на мотиваційному прикладі. Приклад Window / WindowManager кращий, ніж показаний приклад, але занадто розпливчастий. Ця відповідь також не стосується частини інкапсуляції питання.
bames53

4
Так ефективно "друг" існує тому, що C ++ не має поняття пакету, в якому всі члени можуть поділитися деталями реалізації? Мені б дуже цікавий приклад у реальному світі.
weberc2

1
@OMGtechy Вам би цього не потрібно було робити, якщо C ++ мав поняття про пакети, тож це відповідає моєму попередньому твердженню. Ось приклад у Go, який використовує пакети замість друзів для доступу до приватних членів: play.golang.org/p/xnade4GBAL
weberc2

162

На роботі ми використовуємо друзів для тестування коду , широко. Це означає, що ми можемо забезпечити належну інкапсуляцію та приховування інформації для основного коду програми. Але також ми можемо мати окремий тестовий код, який використовує друзів для перевірки внутрішнього стану та даних для тестування.

Досить сказати, що я б не використовував ключове слово друг як важливий компонент вашого дизайну.


Саме це я і використовую. Це або просто встановіть змінні члена захищеними. Прикро, що це не працює для C ++ / CLI :-(
Jon Cage

12
Особисто я би це відвернув. Зазвичай ви тестуєте інтерфейс, тобто чи набір входів дає очікуваний набір результатів. Чому потрібно перевіряти внутрішні дані?
Graeme

55
@Graeme: Оскільки хороший план тестування включає тестування як білого поля, так і чорного.
Бен Войгт

1
Я схильний погоджуватися з @Graeme, як чудово пояснено у цій відповіді .
Алексіс Леклерк

2
@Graeme, можливо, це не внутрішні дані безпосередньо. Я можу бути методом, який виконує певну операцію або завдання над цими даними, коли цей метод є приватним для класу і не повинен бути загальнодоступним, тоді як іншому об'єкту, можливо, доведеться годувати або виводити захищений метод цього класу своїми власними даними.
Френсіс Куглер

93

friendКлючове слово має кілька хороших цілей. Ось два види, які мені відразу видно:

Визначення друга

Визначення Friend дозволяє визначити функцію в класі-області, але функція буде визначатися не як функція-член, а як вільна функція вкладеного простору імен, і вона не буде видимою нормально, за винятком пошуку аргументів. Це робить його особливо корисним для перевантаження оператора:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

Приватний базовий клас CRTP

Іноді ви виявляєте необхідність того, що політиці потрібен доступ до похідного класу:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

Ви знайдете непотрібний приклад для цього у цій відповіді. Ще один код, що використовується в цій відповіді. База CRTP передає цей покажчик, щоб мати доступ до полів даних похідного класу за допомогою покажчиків data-member.


Привіт, я отримую помилку синтаксису (у xcode 4), коли я пробую ваш CRTP. Xcode вважає, що я намагаюся успадкувати шаблон класу. Помилка виникає P<C>в template<template<typename> class P> class C : P<C> {};заяві "Використання шаблону класу C вимагає аргументів шаблону". Чи мали ви ті самі проблеми чи, можливо, знаєте рішення?
Беннедич

@bennedich, на перший погляд, виглядає як помилка, яку ви отримаєте при недостатній підтримці функцій C ++. Що досить поширене серед компіляторів. Використання FlexibleClassвсередині FlexibleClassповинно неявно відноситись до власного типу.
Якк - Адам Невраумон

@bennedich: Правила використання назви шаблону класу зсередини тіла класу змінилися на C ++ 11. Спробуйте ввімкнути режим C ++ 11 у вашому компіляторі.
Ben Voigt

У Visual Studio 2015 додайте цю публікацію: f () {}; f (int_type t): значення (t) {}; Щоб запобігти цій помилці компілятора: помилка C2440: '<function-style-cast>': неможливо перетворити з 'utils :: f :: int_type' в 'utils :: f' Примітка: жоден конструктор не міг прийняти тип джерела чи конструктор Дозвіл перевантаження був неоднозначним
Даміан

41

@roo : Інкапсуляція тут не порушена, оскільки сам клас диктує, хто може отримати доступ до своїх приватних членів. Інкапсуляція буде порушена лише в тому випадку, якщо це може бути викликано поза класом, наприклад, якщо ви operator <<проголосите "Я друг класу foo".

friendзамінює використання public, а не використання private!

Насправді, відповіді на C ++ вже відповідають на це .


14
"друг замінює використання публічного, а не використання приватного!", я другого
Waleed Eissa,

26
@Assaf: так, але FQA, здебільшого, є великою кількістю непослідовних розлючених хитрощів без будь-якої реальної цінності. Частина на friendне є винятком. Єдине справжнє спостереження тут - C ++ забезпечує інкапсуляцію лише під час компіляції. І вам не потрібно більше слів, щоб сказати це. Решта - болоти. Отже, підсумовуючи: цей розділ FQA не варто згадувати.
Конрад Рудольф

12
Більшість з цього FQA - це абсолютно блекс :)
rama-jka toti

1
@Konrad: "Єдине справжнє спостереження тут - це те, що C ++ забезпечує інкапсуляцію лише під час компіляції". Чи забезпечують це будь-які мови під час виконання? Наскільки мені відомо, повернення посилань на приватних членів (і функцій, для мов, які дозволяють вказувати на функції або функції як об'єкти першого класу) дозволено в C #, Java, Python та багатьох інших.
Андре Карон

@ André: JVM та CLR насправді можуть забезпечити це, наскільки я знаю. Я не знаю, чи завжди це робиться, але ви можете нібито захищати пакети / збірки від такого вторгнення (хоча цього ніколи не робив сам).
Конрад Рудольф

27

Канонічний приклад - перевантаження оператора <<. Ще одне поширене використання - дозволити довідковому чи адміністраторському класу отримати доступ до ваших служб.

Ось кілька рекомендацій, які я чув про друзів C ++. Останній особливо запам'ятовується.

  • Ваші друзі не є друзями вашої дитини.
  • Друзі вашої дитини - це не ваші друзі.
  • Тільки друзі можуть торкатися ваших приватних частин.

" Приклад канонічного - перевантажувати оператор <<. " friendЯ думаю, що канонічне використання не використовується .
curiousguy

16

редагувати: читання файлу трохи довше Мені подобається ідея перезавантаження оператора << >> і додавання його як друга цих класів, проте я не впевнений, як це не порушує інкапсуляцію

Як би це порушило інкапсуляцію?

Ви порушуєте інкапсуляцію, коли дозволяєте необмежений доступ до члена даних. Розглянемо наступні класи:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1це , очевидно , НЕ инкапсулируется. Будь-хто може читати та змінювати xв ній. У нас немає способу застосувати будь-який вид контролю доступу.

c2очевидно, капсульований. Немає публічного доступу доx . Все, що ви можете зробити, це викликати fooфункцію, яка виконує якусь змістовну операцію над класом .

c3? Це менш капсульовано? Чи це дозволяє необмежений доступ до x? Чи дозволяє доступ до невідомих функцій?

Ні. Це дозволяє точно за допомогою однієї функції отримати доступ до приватних членів класу. Так само, як і c2зробив. І так само c2, як одна функція, яка має доступ, - це не "якась випадкова, невідома функція", а "функція, зазначена у визначенні класу". Так якc2 ми можемо побачити, лише переглянувши визначення класів, повний список того, хто має доступ.

То як саме це менш капсульоване? Така сама кількість коду має доступ до приватних членів класу. І кожен, хто має доступ, вказаний у визначенні класу.

friendне порушує інкапсуляцію. Це змушує деяких програмістів Java відчувати себе некомфортно, тому що, коли вони говорять "OOP", це насправді означає "Java". Коли вони кажуть "інкапсуляція", вони не означають, що "приватні члени повинні бути захищені від довільного доступу", але "клас Java, де єдині функції, які мають змогу отримати доступ до приватних членів, є членами класу", навіть якщо це повна дурниця для кілька причин .

По-перше, як уже показано, це занадто обмежує. Немає причини, чому дружні методи не повинні дозволяти робити те саме.

По- друге, це не є обмежувальним досить . Розглянемо четвертий клас:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

Це, згідно з вищезазначеним менталітетом Java, ідеально інкапсульоване. І все ж, він дозволяє абсолютно будь-кому читати та змінювати х . Як це навіть має сенс? (натяк: Це не так)

Підсумок: інкапсуляція - це можливість контролювати, які функції можуть отримати доступ до приватних членів. Справа не в тому, де саме знаходяться визначення цих функцій.


10

Ще одна поширена версія прикладу Ендрю, жахливий кодовий пакет

parent.addChild(child);
child.setParent(parent);

Замість того, щоб хвилюватися, якщо обидва рядки завжди виконуються разом і в послідовному порядку, ви можете зробити методи приватними та мати функцію друзів для забезпечення послідовності:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

Іншими словами, ви можете зменшити загальнодоступні інтерфейси та застосувати інваріанти, які перетинають класи та об'єкти у функції друзів.


6
Навіщо кому для цього потрібен друг? Чому не дозволити addChildфункцію члена також встановити батьківську?
Наваз

1
Кращим прикладом може стати setParentдружба, оскільки ви не хочете дозволити клієнтам міняти батьків, оскільки ви керуєте ним у addChild/ removeChildкатегорії функцій.
Ілісар

8

Ви контролюєте права доступу членів і функцій, використовуючи приватне / захищене / публічне право? тож якщо припустити, що ідея кожного з цих трьох рівнів є зрозумілою, тоді повинно бути зрозуміло, що нам щось не вистачає ...

Оголошення члена / функції як захищеного, наприклад, є досить загальним. Ви говорите, що ця функція недосяжна для всіх (за винятком спадкової дитини). А як щодо винятків? кожна система безпеки дозволяє вам мати певний тип "білого списку"?

Тож друг дозволяє вам мати гнучкість ізоляції твердих об'єктів, але дозволяє створити «лазівку» для речей, які, на вашу думку, є виправданими.

Я думаю, люди кажуть, що це не потрібно, тому що завжди є дизайн, який обійдеться без нього. Я думаю, це схоже на обговорення глобальних змінних: ніколи не слід їх використовувати. Завжди існує спосіб обійтися без них ... але насправді ви бачите випадки, коли це в кінцевому підсумку є (майже) найелегантнішим способом. .. Я думаю, що це той самий випадок із друзями.

Це насправді не приносить користі, крім того, що ви можете отримати доступ до змінної члена без використання функції налаштування

добре, це не зовсім так, як на це дивитися. Ідея полягає в тому, щоб контролювати ВООЗ, який може отримати доступ до того, що має, чи не налаштовує функцію , мало спільного з цим.


2
Як проводиться friendлазівка? Він дозволяє методам, перерахованим у класі, отримувати доступ до своїх приватних членів. Він все ще не дозволяє довільному коду отримати доступ до них. Як такий, він нічим не відрізняється від функції публічного члена.
jalf

friend так близько, як ви можете дістатися до доступу на рівні пакетів C # / Java в C ++. @jalf - а як щодо класів друзів (наприклад, фабричного класу)?
Огрійський псалом33

1
@Ogre: Що з ними? Ви все ще спеціально надаєте цьому класу і нікому більше доступ до внутрішніх даних класу. Ви не просто залишаєте ворота відкритими для довільного невідомого коду, щоб підключити свій клас.
jalf

8

Я знайшов зручне місце для доступу до друзів: Unittest приватних функцій.


Але чи можна для цього використовувати і публічну функцію? Яка перевага використання доступу до друзів?
Чжен Ку

@Maverobot Чи можете ви детальніше розглянути своє запитання?
VladimirS

5

Друг стане в нагоді, коли ви будуєте контейнер і хочете реалізувати ітератор для цього класу.


4

У нас з'явилася цікава проблема у компанії, в якій я раніше працював, де ми використовували друга, щоб гідно вплинути. Я працював у нашому базовому відділі, ми створили базову систему рівня двигуна над нашою власною ОС. Всередині у нас була структура класу:

         Game
        /    \
 TwoPlayer  SinglePlayer

Усі ці заняття були частиною основи і підтримувалися нашою командою. Ігри, що виробляються компанією, були побудовані на основі цієї основи, що походить від одного з дітей Ігор. Проблема полягала в тому, що у грі були інтерфейси до різних речей, до яких потрібен доступ SinglePlayer та TwoPlayer, але ми не хотіли виставляти її поза рамками класів. Рішення полягало в тому, щоб зробити ці інтерфейси приватними та дозволити TwoPlayer та SinglePlayer отримати доступ до них через дружбу.

Істинно все це питання можна було б вирішити шляхом кращого впровадження нашої системи, але ми були замкнені у тому, що у нас було.


4

Коротка відповідь буде: використовуйте друга, коли він насправді покращиться інкапсуляцію. Поліпшення читабельності та зручності використання (оператори << і >> є канонічним прикладом) також є вагомою причиною.

Що стосується прикладів вдосконалення інкапсуляції, то класи, спеціально розроблені для роботи з представниками інших класів (тестові класи приходять на думку), є хорошими кандидатами.


" Оператори << і >> є канонічним прикладом ". Ні. Скоріше канонічні лічильники прикладів .
curiousguy

@curiousguy: оператори <<і, >>як правило, є друзями, а не членами, тому що зробити їх членами зроблять їх незручними у використанні. Звичайно, я говорю про випадок, коли цим операторам потрібно отримати доступ до приватних даних; інакше дружба марна.
Горпик

" тому що створення їх членами зробило б їх незручними у використанні ". Очевидно, створення operator<<і operator>>членів класу цінностей замість не членів, або членівi|ostream не забезпечать потрібний синтаксис, і я не пропоную цього. " Я говорю про випадок, коли цим операторам потрібно отримати доступ до приватних даних " Я не зовсім розумію, чому операторам вводу / виводу потрібно мати доступ до приватних членів.
curiousguy

4

Творець C ++ каже, що не порушує жодного принципу інкапсуляції, і я його цитую:

Чи порушує "друг" інкапсуляцію? Ні, це не є. "Друг" - це явний механізм надання доступу, як і членство. Ви не можете (у стандартній відповідній програмі) надати собі доступ до класу, не змінюючи його джерело.

Більш ніж зрозуміло ...


@curiousguy: Навіть у випадку з шаблонами це правда.
Наваз

@Nawaz Шаблон дружби може бути наданий, але кожен може зробити нову часткову або явну спеціалізацію, не змінюючи клас надання дружби. Але будьте обережні з порушеннями ODR, коли це зробите. І не робити цього все одно.
curiousguy

3

Ще одне використання: friend (+ віртуальна спадщина) може бути використаний, щоб уникнути походження з класу (aka: "зробити клас неприйнятним") => 1 , 2

З 2 :

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 

3

Щоб робити TDD багато разів, я використовував ключове слово "friend" в C ++.

Може друг знає про мене все?


Оновлено: я знайшов цю цінну відповідь про ключове слово "друг" з сайту Bjarne Stroustrup .

"Друг" - це явний механізм надання доступу, як і членство.


3

Ви повинні бути дуже обережними щодо того, коли / де ви використовуєте friendключове слово, і, як і ви, я використовував його дуже рідко. Нижче наведено кілька приміток щодо використання friendта альтернатив.

Скажімо, ви хочете порівняти два об'єкти, щоб побачити, чи рівні вони. Ви можете:

  • Використовуйте методи аксесуара для порівняння (перевіряйте кожен ivar і визначайте рівність).
  • Або ви можете отримати доступ до всіх членів безпосередньо, оприлюднивши їх.

Проблема першого варіанту полягає в тому, що це може бути багато аксесуарів, яке (трохи) повільніше, ніж прямий змінний доступ, важче читати та громіздке. Проблема другого підходу полягає в тому, що ви повністю порушите інкапсуляцію.

Що було б добре, якби ми могли визначити зовнішню функцію, яка все-таки могла б отримати доступ до приватних членів класу. Це можна зробити за допомогою friendключового слова:

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

Метод в equal(Beer, Beer)даний час має прямий доступ до aі b«s приватних користувачів (які можуть бути char *brand, float percentAlcoholі т.д. Це досить надуманий приклад, ви б швидше звернутися friendдо перевантаженим == operator, але ми повернемося до цього.

Кілька речей, які слід зазначити:

  • A friendНЕ є функцією члена класу
  • Це звичайна функція зі спеціальним доступом до приватних членів класу
  • Не замінюйте всіх аксесуарів та мутаторів друзями (ви можете все зробити public!)
  • Дружба не є взаємною
  • Дружба не перехідна
  • Дружба не передається у спадок
  • Або, як пояснює FAQ C ++ : "Просто тому, що я надаю вам доступ до дружби до мене, автоматично не надає вашим дітям доступ до мене, не автоматично надає вашим друзям доступ до мене і не дає автоматично мені доступ до вас" . "

Я справді використовую лише friendsтоді, коли це набагато важче зробити це іншим способом. В якості іншого прикладу, функції багато чого вектора математика часто створюються в friendsзв'язку з сумісністю Mat2x2, Mat3x3, Mat4x4, Vec2, Vec3, Vec4і т.д. І це просто так набагато простіше бути друзями, а не повинен використовувати аксессор всюди. Як зазначалося, friendчасто корисно при застосуванні до <<(дійсно зручно для налагодження) >>та, можливо, ==оператора, але також може бути використане для чогось подібного:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

Як я кажу, я взагалі не використовую friendдуже часто, але раз у раз це саме те, що потрібно. Сподіваюся, це допомагає!


2

Що стосується оператора << і оператора >>, то немає вагомих причин для того, щоб подружити цих операторів. Це правда, що вони не повинні бути членами функцій, але вони також не повинні бути друзями.

Найкраще це створити публічні функції друку (ostream &) та читання (istream &). Потім запишіть оператор << і оператор >> в рамках цих функцій. Це дає додаткову перевагу, що дозволяє зробити ті функції віртуальними, що забезпечує віртуальну серіалізацію.


" Що стосується оператора << і оператора >>, то немає вагомих причин заводити цих операторів друзями. " Абсолютно правильно. " Це дає додаткову перевагу, що дозволяє зробити ці функції віртуальними ". Якщо відповідний клас призначений для виведення, так. Інакше навіщо турбуватись?
curiousguy

Я дійсно не розумію, чому цю відповідь було опромінено двічі - і навіть без пояснень! Це грубо.
допитливо

віртуальна додала б хіт
perf,

2

Я використовую лише ключове слово friend для лише тестування захищених функцій. Деякі скажуть, що не слід перевіряти захищені функціональні можливості. Я, однак, вважаю цей дуже корисним інструментом при додаванні нових функціональних можливостей.

Однак я не використовую ключове слово безпосередньо в деклараціях класу, натомість використовую чудовий хакер-шаблон, щоб досягти цього:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

Це дозволяє мені зробити наступне:

friendMe(this, someClassInstance).someProtectedFunction();

Працює над GCC та MSVC принаймні.


2

У C ++ ключове слово "друг" корисне для перевантаження оператора та створення мосту.

1.) Ключове слово Друга при перевантаженні оператора:
Прикладом перевантаження оператора є: Скажімо, у нас є клас "Точка", який має дві поплавкові змінної
"x" (для x-координати) та "y" (для y-координати). Тепер нам доведеться перевантажувати "<<"(оператор видобутку) таким, що якщо ми зателефонуємо"cout << pointobj" він надрукує координати x і y (де pointobj є об'єктом класу Point). Для цього у нас є два варіанти:

   1.Завантажте функцію "оператор << ()" у класі "ostream".
   2.Завантажте функцію "оператор << ()" у класі "Точка".
Тепер перший варіант не є гарним, тому що якщо нам знову потрібно перевантажити цього оператора для якогось іншого класу, тоді нам доведеться знову внести зміни в "ostream" клас.
Ось чому другий - найкращий варіант. Тепер компілятор може викликати "operator <<()"функцію:

   1. Використання ostream об'єкта cout.As: cout.operator << (Pointobj) (форма класу ostream). 
2.Виклик без об'єкта. Як: оператор << (cout, Pointobj) (від класу Point).

Тому що ми здійснили перевантаження в класі Point. Отже, щоб викликати цю функцію без об'єкта, ми повинні додати "friend"ключове слово, оскільки ми можемо викликати функцію друга без об’єкта. Тепер декларація функції буде виглядати як:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Ключове слово друга при створенні мосту:
Припустимо, ми повинні зробити функцію, в якій нам потрібно отримати доступ до приватного члена двох або більше класів (зазвичай його називають "міст"). Як це зробити:
Щоб отримати доступ до приватного члена класу, він повинен бути членом цього класу. Тепер для доступу до приватного члена іншого класу кожен клас повинен оголосити цю функцію як функцію друга. Наприклад: Припустимо, є два класу A і B. Функція "funcBridge()"хоче отримати доступ до приватного члена обох класів. Тоді обидва класу повинні оголосити "funcBridge()"як:
friend return_type funcBridge(A &a_obj, B & b_obj);

Я думаю, що це допоможе зрозуміти ключове слово друга.


2

Як зазначається в декларації про друзів :

Декларація про друзі з'являється в тілі класу і надає функції або іншому класу доступ до приватних і захищених членів класу, де відображається декларація про друга.

Тож як нагадування, у деяких відповідях є технічні помилки, які говорять, що friendвідвідувати можуть лише захищені члени.


1

Приклад дерева - досить хороший приклад: наявність об'єкта, реалізованого в декількох різних класах, не маючи спадкових відносин.

Можливо, вам це також може знадобитися, щоб конструктор захистив і змусив людей використовувати вашу фабрику "друга".

... Гаразд, відверто кажучи, можна без цього жити.


1

Щоб робити TDD багато разів, я використовував ключове слово "friend" в C ++.
Може друг знає про мене все?

Ні, це єдиний спосіб дружби: `(


1

Один конкретний екземпляр, який я використовую, friend- це для створення класів Singleton . friendКлючове слово дозволяє мені створити функцію доступу, яка є більш коротким , ніж завжди мати «GetInstance ()» метод на класі.

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

Це може бути справою смаку, але я не думаю, що заощадження кількох натискань клавіш виправдовує використання друга тут. GetMySingleton () повинен стати статичним методом класу.
Горпик

Приватний c-tor забороняє функцію, що не стосується друга, для екземпляра MySingleton, тому тут потрібне ключове слово друзів.
JBRWilkinson

@Gorpik " Це може бути справою смаку, але я не думаю, що заощадження кількох натискань клавіш виправдовує використання друга ". Це так. У всякому разі, friendце НЕ потрібно особливе «виправдання», при додаванні функції члена немає.
curiousguy

Синглтони все одно вважаються поганою практикою (Google "одиночний шкідливий", і ви отримаєте безліч результатів, як це . Я не думаю, що використання функції для впровадження антипатерн може вважатися хорошим використанням цієї функції.
weberc2

1

Функції та класи друзів забезпечують прямий доступ до приватних та захищених членів класу, щоб уникнути порушення інкапсуляції в загальному випадку. Найбільше використання використовується в ostream: ми хотіли б мати можливість вводити:

Point p;
cout << p;

Однак це може зажадати доступу до приватних даних Point, тому ми визначаємо перевантажений оператор

friend ostream& operator<<(ostream& output, const Point& p);

Однак очевидні наслідки інкапсуляції. По-перше, тепер клас друзів або функція має повний доступ до ВСІХ членів класу, навіть тих, які не відповідають його потребам. По-друге, реалізація класу та друга тепер переплутана до того, що внутрішня зміна класу може зламати друга.

Якщо ви розглядаєте друга як розширення класу, то це не проблема, логічно кажучи. Але, в такому випадку, чому потрібно було в першу чергу висловлювати друга.

Щоб досягти того самого, що мають досягти "друзі", але не порушуючи інкапсуляцію, можна зробити це:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

Інкапсуляція не порушена, клас B не має доступу до внутрішньої реалізації в A, але результат такий же, як якщо б ми оголосили B другом А. Компілятор оптимізує виклики функцій, тож це призведе до того ж інструкції як прямий доступ.

Я думаю, що використання "друга" - це просто ярлик із суперечливою вигодою, але певна вартість.


1

Це може не бути фактичним випадком використання, але може допомогти проілюструвати використання друга між класами.

Будинок клубу

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

Класи учасників

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

Зручності

class Amenity{};

Якщо ви подивитесь на зв’язок цих класів тут; ClubHouse має безліч різних типів членства та доступу до членства. Усі члени походять від супер або базового класу, оскільки всі вони мають спільний ідентифікатор, а перелічений тип, який є загальним, а зовнішні класи можуть отримувати доступ до своїх ідентифікаторів та типів через функції доступу, які знаходяться в базовому класі.

Однак завдяки такій ієрархії Учасників та її похідних класів та їх зв’язків із класом ClubHouse єдиним із похідних класів, що має "особливі привілеї", є клас VIPMember. Базовий клас та інші два похідні класи не можуть отримати доступ до методу joinVIPEvent () ClubHouse, проте клас VIP Member має таку привілей, як ніби він має повний доступ до цієї події.

Тож з VIPMember та ClubHouse це двостороння вулиця доступу, де інші класи членів обмежені.


0

При впровадженні алгоритмів дерев для класу рамковий код, який нам дав проф, мав клас дерева як друга класу вузлів.

Це насправді не приносить користі, крім того, що ви можете отримати доступ до змінної члена без використання функції налаштування.


0

Ви можете використовувати дружбу, коли різні класи (не успадковуючи один від іншого) використовують приватних або захищених членів іншого класу.

Типовими випадками використання функцій друзів є операції, які проводяться між двома різними класами, що мають доступ до приватних або захищених членів обох.

з http://www.cplusplus.com/doc/tutorial/inheritance/ .

Ви можете бачити цей приклад, коли метод, який не є членом, звертається до приватних членів класу. Цей метод повинен бути оголошений у цьому самому класі як товариш класу.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

0

Можливо, я пропустив щось із наведених вище відповідей, але ще одна важлива концепція інкапсуляції - це приховування реалізації. Скорочення доступу до приватних членів даних (деталі реалізації класу) дозволяє значно простіше змінити код пізніше. Якщо друг безпосередньо отримує доступ до приватних даних, будь-які зміни в полях даних про реалізацію (приватні дані) порушують код доступу до цих даних. Використання методів доступу в основному усуває це. Дуже важливо, я думаю.


-1

Ви могли б дотримуватися принципів строгої і найчистішої ООП і переконайтеся , що ні один з членів даних для будь-якого класу , навіть не мають аксессор , так що всі об'єкти повинні бути тільки ті , які можуть знати про своїх даних з єдиним способом , щоб впливати на них з допомогою непрямих повідомлень , тобто методи.

Але навіть у C # є ключове слово з внутрішньою видимістю, а для деяких програм у Java є доступність пакету за замовчуванням . C ++ насправді наближається до ідеалу OOP, зводячи до мінімуму компроміс видимості в клас, конкретизуючи , який саме клас та лише інші класи можуть бачити в ньому.

Я дійсно не використовую C ++, але якби у C # був друг s, я б це замість внутрішнього модифікатора асемблера , яким я фактично користуюся багато. Він насправді не порушує інкапсуляцію, оскільки одиниця розгортання в .NET - це збірка.

Але тоді є InternalsVisibleTo атрибут (otherAssembly) , який діє як крос-збірка іншого механізм. Microsoft використовує це для збірок візуальних дизайнерів .


-1

Друзі також корисні для зворотних дзвінків. Ви можете реалізувати зворотні виклики як статичні методи

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

де callbackдзвінки localCallbackвнутрішньо, іclientData є ваш примірник. На мою думку,

або ...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

Це дозволяє, щоб друг визначався суто в cpp як функції стилю c, а не захаращував клас.

Подібним чином, я часто бачив зразок - це все реально викласти приватних членів класу в інший клас, який оголошується в заголовку, визначеному в cpp, і закріплений фрейдом. Це дозволяє кодеру приховати велику складність та внутрішню роботу класу від користувача заголовка.

У заголовку:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

У cpp,

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

Стає простіше приховати речі, які вниз за течією не повинні бачити таким чином.


1
Чи не буде інтерфейсом чистішим способом цього досягти? Що перешкоджає комусь шукати MyFooPrivate.h?
JBRWilkinson

1
Що ж, якщо ви використовуєте приватне та громадське для зберігання секретів, ви легко переможете. Маючи на увазі, «приховуючи», користувачеві MyFoo насправді не потрібно бачити приватних членів. Крім цього, корисно підтримувати сумісність з ABI. Якщо ви робите _private вказівник, приватна реалізація може змінюватися скільки завгодно, не торкаючись загальнодоступного інтерфейсу, тим самим зберігаючи сумісність ABI.
шаш

Ви посилаєтесь на ідіому PIMPL; мета якої не є додатковою інкапсуляцією, як ви, як ви говорите, але перемістити деталі реалізації з заголовка, щоб змінити деталі реалізації не змусити перекомпілювати клієнтський код. Крім того, не потрібно використовувати товариша для реалізації цієї ідіоми.
weberc2

Ну так. Основна його мета - переміщення деталей щодо впровадження. Друг там корисно звертатися до приватних членів у публічному класі з приватного чи навпаки.
шаш
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.