Когда вы должны использовать «друг» в C ++? - PullRequest
334 голосов
/ 20 августа 2008

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

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


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

Ответы [ 30 ]

4 голосов
/ 11 февраля 2010

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

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

Более чем ясно ...

4 голосов
/ 16 января 2009

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

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

3 голосов
/ 07 сентября 2009

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

С 2 :

 class Fred;

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

 class Fred : private virtual FredBase {
 public:
   ...
 }; 
3 голосов
/ 05 сентября 2008

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

         Game
        /    \
 TwoPlayer  SinglePlayer

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

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

3 голосов
/ 20 августа 2008

Для многократного использования TDD я использовал ключевое слово "друг" в C ++.

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


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

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

2 голосов
/ 07 сентября 2009

Я использую ключевое слово 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 голосов
/ 29 декабря 2012

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

Допустим, вы хотите сравнить два объекта, чтобы увидеть, равны ли они. Вы можете либо:

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

Проблема с первой опцией заключается в том, что это может быть МНОГО аксессоров, которые (немного) медленнее, чем прямой доступ к переменным, труднее для чтения и громоздки. Проблема второго подхода заключается в том, что вы полностью нарушаете инкапсуляцию.

Что было бы хорошо, если бы мы могли определить внешнюю функцию, которая могла бы по-прежнему получать доступ к закрытым членам класса. Мы можем сделать это с помощью ключевого слова friend:

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

Метод equal(Beer, Beer) теперь имеет прямой доступ к a и b закрытым членам (которые могут быть 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 голосов
/ 21 августа 2008

Что касается оператора << и оператора >>, то нет веских причин дружить с этими операторами. Это правда, что они не должны быть функциями-членами, но они также не должны быть друзьями.

Лучше всего создать открытые функции печати (ostream &) и чтения (istream &). Затем напишите оператор << и оператор >> в терминах этих функций. Это дает дополнительное преимущество, позволяя вам сделать эти функции виртуальными, что обеспечивает виртуальную сериализацию.

1 голос
/ 20 августа 2008

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

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

... Хорошо, честно говоря, вы можете жить без него.

1 голос
/ 20 августа 2008

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

Нет, дружба только в одну сторону: `(

...