Когда вы должны использовать «друг» в C ++?


354

Я читал часто задаваемые вопросы по C ++ и мне было интересно узнать об этом friendобъявлении. Лично я никогда не использовал это, однако я заинтересован в изучении языка.

Что является хорошим примером использования friend?


Немного дольше читая FAQ, мне нравится идея << >>перегрузки и добавления операторов в друзья этих классов. Однако я не уверен, как это не нарушает инкапсуляцию. Когда эти исключения могут оставаться в рамках строгости ООП?


5
Хотя я согласен с ответом, что класс друга не обязательно является плохой вещью, я склонен относиться к нему как к небольшому коду. Это часто, хотя и не всегда, указывает на необходимость пересмотра иерархии классов.
Mawg говорит восстановить Монику

1
Вы бы использовали класс друга, где уже есть тесная связь. Вот для чего это сделано. Например, таблица базы данных и ее индексы тесно связаны. Когда таблица изменяется, все ее индексы должны быть обновлены. Таким образом, класс DBIndex объявил бы DBTable своим другом, чтобы DBTable мог напрямую обращаться к внутренним объектам индекса. Но не было бы публичного интерфейса с DBIndex; не имеет смысла даже читать индекс.
shawnhcorey

«Пуристы» ООП с небольшим практическим опытом утверждают, что друг нарушает принципы ООП, потому что класс должен быть единственным хранителем своего частного состояния. Это нормально, пока вы не столкнетесь с общей ситуацией, когда двум классам необходимо поддерживать общее приватное состояние.
каал

Ответы:


335

Во-первых (ИМО) не слушайте людей, которые говорят, что friendэто бесполезно. Это полезно. Во многих ситуациях у вас будут объекты с данными или функциями, которые не предназначены для публичного доступа. Это особенно верно для больших кодовых баз со многими авторами, которые могут только поверхностно знакомы с различными областями.

Существуют альтернативы спецификатору друга, но часто они громоздки (конкретные классы уровня cpp / маскированные определения типов) или не защищены от ошибок (комментарии или соглашения об именах функций).

На ответ;

Спецификатор friendразрешает назначенному классу доступ к защищенным данным или функциям в классе, делающем оператор Friend. Например, в приведенном ниже коде любой может попросить ребенка назвать свое имя, но только мать и ребенок могут изменить имя.

Вы можете продолжить этот простой пример, рассмотрев более сложный класс, такой как Window. Вполне вероятно, что в Window будет много элементов функций / данных, которые не должны быть общедоступными, но необходимы для связанного класса, такого как 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
В качестве дополнительного примечания, C ++ FAQ упоминает, что friend улучшает инкапсуляцию. friendпредоставляет избирательный доступ к членам, как и protectedделает. Любой детальный контроль лучше, чем предоставление публичного доступа. Другие языки также определяют механизмы избирательного доступа, рассмотрим C # internal. Наиболее негативная критика вокруг использования friendсвязана с более тесной связью, которая обычно считается плохой вещью. Однако в некоторых случаях более тесная связь - именно то, что вам нужно, и friendдает вам эту силу.
Андре Карон

5
Не могли бы вы сказать больше о (конкретные классы уровня cpp) и (маскированные typedefs), Эндрю ?
ОмарОтман

18
Этот ответ, кажется, более сфокусирован на объяснении того, что friendявляется, а не на мотивирующем примере. Пример Window / WindowManager лучше, чем показанный пример, но слишком расплывчатый. Этот ответ также не касается инкапсуляции части вопроса.
bames53

4
Так эффективно существует «друг», потому что C ++ не имеет понятия о пакете, в котором все участники могут делиться деталями реализации? Я был бы действительно заинтересован в реальном примере.
weberc2

1
Я думаю, что пример «Мать / дитя» вызывает сожаление. Эти имена подходят для экземпляров, но не для классов. (Проблема показывает, есть ли у нас 2 мамы, и у каждой есть свой ребенок).
Джо Со

162

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

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


Это именно то, для чего я его использую. Это или просто установите переменные-члены для защищенных. Это просто позор, это не работает для C ++ / CLI :-(
Джон Кейдж

12
Лично я бы препятствовал этому. Обычно вы тестируете интерфейс, т.е. дает ли набор входов ожидаемый набор выходных данных. Зачем вам нужно проверять внутренние данные?
Грэм

55
@ Grame: Потому что хороший план тестирования включает в себя тестирование как белого, так и черного ящиков.
Бен Фойгт

1
Я склонен согласиться с @Graeme, как прекрасно объясняется в этом ответе .
Алексис Леклерк

2
@ Грэм, это не может быть внутренняя информация напрямую. Я могу быть методом, который выполняет определенную операцию или задачу над этими данными, когда этот метод является закрытым для класса и не должен быть общедоступным, в то время как некоторым другим объектам может потребоваться передать или заполнить защищенный метод этого класса своими собственными данными.
Фрэнсис Куглер

93

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 преобразует этот указатель, чтобы иметь возможность доступа к полям данных производного класса с помощью указателей на данные-члена.


Привет, я получаю синтаксическую ошибку (в 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 в вашем компиляторе.
Бен Фойгт

В 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 ++ FAQ отвечает уже на это .


14
«Друг заменяет публичное использование, а не частное!», второе, я
Валид Эйсса

26
@Assaf: да, но FQA - это, по большей части, много бессвязного злобного бреда без какой-либо реальной ценности. Эта часть friendне является исключением. Единственное реальное наблюдение здесь - это то, что C ++ обеспечивает инкапсуляцию только во время компиляции. И вам не нужно больше слов, чтобы сказать это. Остальное чушь. Итак, в заключение: этот раздел FQA не стоит упоминать.
Конрад Рудольф

12
Большая часть этого FQA полная блэк :)
rama-jka toti

1
@Konrad: «Единственное реальное наблюдение здесь - это то, что C ++ обеспечивает инкапсуляцию только во время компиляции». Какие-нибудь языки гарантируют это во время выполнения? Насколько я знаю, возвращение ссылок на закрытые члены (и функции для языков, которые позволяют указатели на функции или функции в качестве объектов первого класса) разрешено в C #, Java, Python и многих других.
Андре Карон

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

27

Канонический пример - перегрузка оператора <<. Другим распространенным применением является предоставление доступа к вашим внутренним компонентам помощникам или администраторам.

Вот несколько рекомендаций, которые я слышал о друзьях C ++. Последний особенно запоминающийся.

  • Ваши друзья не друзья вашего ребенка.
  • Друзья вашего ребенка не ваши друзья.
  • Только друзья могут касаться ваших личных частей.

« Канонический пример - перегрузка оператора <<. » Канонический отказ от использования, friendя думаю.
любопытный парень

16

edit: Чтение faq немного дольше Мне нравится идея перегрузки оператора << >> и добавления в качестве друга этих классов, однако я не уверен, как это не нарушает инкапсуляцию

Как это нарушит инкапсуляцию?

Вы нарушаете инкапсуляцию, когда разрешаете неограниченный доступ к элементу данных. Рассмотрим следующие классы:

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 чувствовать себя неловко, потому что когда они говорят «ООП», они на самом деле означают «Java». Когда они говорят «Инкапсуляция», они не означают «частные члены должны быть защищены от произвольного доступа», а «класс Java, где единственными функциями, которые могут получить доступ к закрытым членам, являются члены класса», хотя это полная чушь для несколько причин .

Во-первых, как уже показано, это слишком ограничивает. Нет причин, по которым методы друзей не должны делать то же самое.

Во- вторых, это не является ограничительным достаточно . Рассмотрим четвертый класс:

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

Это, согласно вышеупомянутому менталитету Java, прекрасно инкапсулировано. И все же, это позволяет абсолютно любому читать и изменять x . Как это вообще имеет смысл? (подсказка: это не так)

Итог: инкапсуляция - это возможность контролировать, какие функции могут получить доступ к закрытым членам. Речь идет не о том, где именно находятся определения этих функций.


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

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

1
@Ogre: А как насчет них? Вы все еще специально предоставляете этот класс, и никто больше не имеет доступа к внутренним компонентам класса. Вы не просто оставляете ворота открытыми для произвольного неизвестного кода, чтобы напортачить с вашим классом.
Джалф

8

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


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

@Maverobot Не могли бы вы уточнить свой вопрос?
VladimirS

5

Friend пригодится, когда вы создаете контейнер и хотите реализовать итератор для этого класса.


4

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

         Game
        /    \
 TwoPlayer  SinglePlayer

Все эти классы были частью структуры и поддерживаются нашей командой. Игры, производимые компанией, были построены на основе этого фреймворка, созданного одним из детей Games. Проблема заключалась в том, что у Game были интерфейсы к различным вещам, к которым SinglePlayer и TwoPlayer требовался доступ, но мы не хотели, чтобы они были доступны вне рамок классов. Решением было сделать эти интерфейсы частными и разрешить TwoPlayer и SinglePlayer доступ к ним через дружбу.

По правде говоря, весь этот вопрос мог бы быть решен лучшей реализацией нашей системы, но мы были заперты в том, что имели.


4

Короткий ответ будет: использовать друга когда это действительно улучшается инкапсуляцию. Улучшение читаемости и удобства использования (операторы << и >> являются каноническим примером) также является хорошей причиной.

Что касается примеров улучшения инкапсуляции, то хорошими кандидатами являются классы, специально предназначенные для работы с внутренними компонентами других классов (тестовые классы приходят на ум).


« операторы << и >> являются каноническим примером ». Нет. Скорее примеры канонического счетчика .
любопытный парень

@curiousguy: операторы <<и, >>как правило, друзья, а не участники, потому что делать из них членов будет неудобно. Конечно, я говорю о случае, когда этим операторам необходим доступ к частным данным; иначе дружба бесполезна.
Горпик

« потому что создание их членами сделало бы их неудобными для использования». Очевидно, что создание operator<<и operator>>члены класса значения вместо не членов или членов i|ostreamне будут обеспечивать желаемый синтаксис, и я не предлагаю его. « Я говорю о случае, когда этим операторам нужен доступ к частным данным ». Я не совсем понимаю, почему операторам ввода / вывода нужен доступ к закрытым членам.
любопытный парень

4

Создатель C ++ говорит, что не нарушает принцип инкапсуляции, и я процитирую его:

«Друг» нарушает инкапсуляцию? Нет. «Друг» - это явный механизм предоставления доступа, как и членство. Вы не можете (в стандартной соответствующей программе) предоставить себе доступ к классу без изменения его источника.

Более чем понятно ...


@curiousguy: даже в случае с шаблонами это правда.
Наваз

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

3

Другое использование: friend (+ виртуальное наследование) может быть использовано, чтобы избежать наследования от класса (иначе: «сделать класс неразборчивым») => 1 , 2

Из 2 :

 class Fred;

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

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

3

Чтобы сделать TDD много раз, я использовал ключевое слово «друг» в C ++.

Может ли друг знать обо мне все?


Обновлено: я нашел этот ценный ответ о ключевом слове "друг" с сайта Бьярна Страуструпа .

«Друг» - это явный механизм предоставления доступа, как и членство.


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!)
  • Дружба не взаимна
  • Дружба не переходная
  • Дружба не наследуется
  • Или, как объясняется в C ++ FAQ : «Только то, что я предоставляю вам дружеский доступ ко мне, автоматически не дает вашим детям доступ ко мне, автоматически не предоставляет вашим друзьям доступ ко мне и автоматически не дает мне доступ к вам». «.

Я действительно использую только 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 &). Затем напишите оператор << и оператор >> в терминах этих функций. Это дает дополнительное преимущество, позволяя вам сделать эти функции виртуальными, что обеспечивает виртуальную сериализацию.


« Относительно оператора << и оператора >> нет веских причин дружить с этими операторами ». Абсолютно правильно. « Это дает дополнительное преимущество, позволяя вам сделать эти функции виртуальными », если рассматриваемый класс предназначен для деривации, да. Иначе зачем?
любопытный парень

Я действительно не понимаю, почему этот ответ был отклонен дважды - и даже без объяснения причин! Это грубо.
любопытный парень

Виртуальная добавит хит производительности, который может быть довольно большим в сериализации
Пол

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.) Ключевое слово Friend при перегрузке оператора:
Пример перегрузки оператора: Допустим, у нас есть класс «Point», который имеет две переменные с плавающей запятой
«x» (для координаты x) и «y» (для координаты y). Теперь мы должны перегрузить "<<"(оператор извлечения), что если мы вызываем"cout << pointobj" его, он выведет координаты x и y (где pointobj - это объект класса Point). Для этого у нас есть два варианта:

   1. Перегрузить функцию «operator << ()» в классе «ostream».
   2. Перегрузить функцию «operator << ()» в классе «Point».
Теперь первый вариант не годится, потому что если нам нужно снова перегрузить этот оператор для какого-то другого класса, мы должны снова внести изменения в класс «ostream».
Вот почему второй вариант самый лучший. Теперь компилятор может вызывать "operator <<()"функцию:

   1. Использование объекта ostream cout.As: cout.operator << (Pointobj) (форма класса ostream). 
2. Вызов без объекта. Как: operator << (cout, Pointobj) (из класса Point).

Поскольку мы реализовали перегрузку в классе Point. Таким образом, чтобы вызвать эту функцию без объекта, мы должны добавить "friend"ключевое слово, потому что мы можем вызвать функцию друга без объекта. Теперь объявление функции будет следующим:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Ключевое слово Friend при создании моста:
предположим, нам нужно создать функцию, в которой мы должны получить доступ к закрытому члену двух или более классов (обычно называемых «мостом»). Как это сделать:
чтобы получить доступ к закрытому члену класса, он должен быть членом этого класса. Теперь для доступа к закрытому члену другого класса каждый класс должен объявить эту функцию как функцию друга. Например: предположим, есть два класса A и B. Функция "funcBridge()"хочет получить доступ к закрытому члену обоих классов. Тогда оба класса должны объявить "funcBridge()"как:
friend return_type funcBridge(A &a_obj, B & b_obj);

Я думаю, что это поможет понять ключевое слово друга.


2

В качестве ссылки для объявления друзей говорится:

Объявление друга появляется в теле класса и предоставляет функции или другому классу доступ к закрытым и защищенным членам класса, в котором появляется объявление друга.

Так что, как напоминание, в некоторых ответах есть технические ошибки, которые говорят, что friendмогут посещать только защищенные участники.


1

Пример дерева - довольно хороший пример: реализация объекта в нескольких разных классах без наследования.

Возможно, вам также может понадобиться защита конструктора и принуждение людей использовать вашу «фабрику друзей».

... Ладно, честно говоря, вы можете жить без этого.


1

Чтобы сделать TDD много раз, я использовал ключевое слово «друг» в 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, поэтому здесь необходимо ключевое слово friend.
JBRWilkinson

@Gorpik " Это может быть дело вкуса, но я не думаю, что сохранение нескольких нажатий клавиш оправдывает использование друга здесь. " Это делает. Во всяком случае, friendэто не нужно особое «оправдание», при добавлении функции члена нет.
любопытный парень

Одиночки считается плохой практикой в любом случае (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 другом A. Компилятор оптимизирует вызовы функций, поэтому это приведет к тому же результату. инструкции как прямой доступ.

Я думаю, что с помощью «друг» просто ярлык с спорной выгодой, но определенной платой.


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. Базовый класс и другие 2 производных класса не могут получить доступ к методу joinVIPEvent () ClubHouse, однако у класса VIP-члена есть такая привилегия, как если бы он имел полный доступ к этому событию.

Таким образом, с 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 ++ приближается к идеалу ООП, минимизируя компромисс видимости в классе, точно указав , какой другой класс и только другие классы могут его видеть.

Я на самом деле не использую C ++, но если бы у C # были друзья, я бы это использовал вместо внутреннего модификатора сборки-глобального , который я действительно часто использую. На самом деле это не нарушает инкапсуляцию, потому что единица развертывания в .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.