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

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

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


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

20.08.2008 05:29:32
Хотя я согласен с ответом, что класс друга не обязательно является «Плохой вещью», я склонен относиться к нему как к небольшому коду. Это часто, хотя и не всегда, указывает на необходимость пересмотра иерархии классов.
Mawg says reinstate Monica 23.09.2014 07:03:33
Вы бы использовали класс друга, где уже есть тесная связь. Вот для чего это сделано. Например, таблица базы данных и ее индексы тесно связаны. Когда таблица изменяется, все ее индексы должны быть обновлены. Таким образом, класс DBIndex объявил бы DBTable как друга, чтобы DBTable мог обращаться к внутренним объектам индекса напрямую. Но не было бы публичного интерфейса с DBIndex; не имеет смысла даже читать индекс.
shawnhcorey 27.10.2017 12:56:28
«Пуристы» ООП с небольшим практическим опытом утверждают, что друг нарушает принципы ООП, потому что класс должен быть единственным хранителем своего частного состояния. Это нормально, пока вы не столкнетесь с общей ситуацией, когда двум классам необходимо поддерживать общее приватное состояние.
kaalus 11.03.2020 15:52:00
30 ОТВЕТОВ
РЕШЕНИЕ

Во-первых (ИМО) не слушайте людей, которые говорят, что 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 );
};
334
17.01.2017 14:09:28
В качестве дополнительного примечания, C ++ FAQ упоминает, что friend улучшает инкапсуляцию. friendпредоставляет избирательный доступ к членам, как и protectedделает. Любой детальный контроль лучше, чем предоставление публичного доступа. Другие языки также определяют механизмы избирательного доступа, рассмотрим C # internal. Наиболее негативная критика вокруг использования friendсвязана с более тесной связью, которая обычно считается плохой вещью. Однако в некоторых случаях более тесная связь - это именно то, что вам нужно, и friendдает вам эту силу.
André Caron 21.10.2010 18:28:44
Не могли бы вы рассказать подробнее о (конкретные классы уровня cpp) и (маскированные typedefs), Эндрю ?
OmarOthman 22.11.2011 07:47:17
Этот ответ, кажется, более сфокусирован на объяснении того, что friendявляется, а не на мотивирующем примере. Пример Window / WindowManager лучше, чем показанный пример, но слишком расплывчатый. Этот ответ также не касается инкапсуляции части вопроса.
bames53 28.12.2012 23:56:44
Так эффективно существует «друг», потому что в C ++ нет понятия пакета, в котором все участники могут делиться деталями реализации? Я был бы действительно заинтересован в реальном примере.
weberc2 17.06.2014 18:48:01
@OMGtechy Вам бы не пришлось этого делать, если бы в C ++ было представление о пакетах, поэтому это согласуется с моим предыдущим утверждением. Вот пример в Go, который использует пакеты вместо друзей для доступа к закрытым членам: play.golang.org/p/xnade4GBAL
weberc2 31.07.2014 15:03:09

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

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

0
20.08.2008 05:33:58

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

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

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

1
20.08.2008 05:51:21

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

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

Таким образом, друг позволяет вам иметь гибкую изоляцию твердого объекта, но позволяет создать «лазейку» для вещей, которые вы считаете оправданными.

Я думаю, что люди говорят, что это не нужно, потому что всегда есть дизайн, который обойдется без него. Я думаю, что это похоже на обсуждение глобальных переменных: вы никогда не должны их использовать. Всегда есть способ обойтись без них ... но в действительности вы видите случаи, когда это оказывается (почти) самым элегантным способом. .. Я думаю, что это то же самое с друзьями.

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

хорошо, что это не совсем способ смотреть на это. Идея состоит в том, чтобы контролировать ВОЗ, которая может получить доступ к тому, что наличие или отсутствие функции настройки имеет мало общего с ней.

8
20.08.2008 06:10:58
Как friendлазейка? Это позволяет методам, перечисленным в классе, получить доступ к своим закрытым членам. Это все еще не позволяет произвольному коду получить к ним доступ. Как таковая она ничем не отличается от публичной функции-члена.
jalf 7.09.2009 09:26:30
друг настолько близок, насколько вы можете получить доступ к пакетному уровню C # / Java в C ++. @jalf - а как насчет классов друзей (например, заводского класса)?
Ogre Psalm33 20.10.2010 13:46:31
@Ogre: А как насчет них? Вы все еще специально предоставляете этот класс, и никто больше не имеет доступа к внутренним компонентам класса. Вы не просто оставляете ворота открытыми для произвольного неизвестного кода, чтобы напортачить с вашим классом.
jalf 20.10.2010 22:55:13

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

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

  • Ваши друзья не друзья вашего ребенка.
  • Друзья вашего ребенка не ваши друзья.
  • Только друзья могут касаться ваших личных частей.
27
20.08.2008 07:05:04
« Канонический пример - перегрузка оператора <<. » Канонический отказ от использования, friendя думаю.
curiousguy 23.12.2011 07:06:06

@roo : Инкапсуляция здесь не нарушена, потому что сам класс диктует, кто может получить доступ к своим закрытым членам. Инкапсуляция будет нарушена только в том случае, если это может быть вызвано извне класса, например, если вы operator <<объявите «Я друг класса foo».

friendзаменяет использование public, а не использование private!

На самом деле, C ++ FAQ отвечает уже на это .

41
23.05.2017 12:34:45
«Друг заменяет публичное использование, а не частное!», второе, я
Waleed Eissa 10.05.2009 05:56:52
@Assaf: да, но FQA - это, по большей части, много бессвязного злобного бреда, не имеющего никакой реальной ценности. Эта часть friendне является исключением. Единственное реальное наблюдение здесь - это то, что C ++ обеспечивает инкапсуляцию только во время компиляции. И вам не нужно больше слов, чтобы сказать это. Остальное чушь. Итак, в заключение: этот раздел FQA не стоит упоминать.
Konrad Rudolph 27.12.2009 21:28:18
Большая часть этого FQA полная блэк :)
rama-jka toti 15.01.2010 07:19:23
@Konrad: «Единственное реальное наблюдение здесь - это то, что C ++ обеспечивает инкапсуляцию только во время компиляции». Какие-нибудь языки гарантируют это во время выполнения? Насколько я знаю, возврат ссылок на закрытые члены (и функции для языков, которые позволяют указатели на функции или функции в качестве объектов первого класса) разрешен в C #, Java, Python и многих других.
André Caron 21.10.2010 18:32:36
@ Андре: насколько я знаю, JVM и CLR действительно могут это обеспечить. Я не знаю, всегда ли это делается, но якобы вы можете защитить пакеты / сборки от такого вторжения (хотя сам никогда этого не делал).
Konrad Rudolph 21.10.2010 21:01:56

Вы могли бы придерживаться самых строгих и чистых принципов ООП и гарантировать, что ни один элемент данных для какого-либо класса даже не имеет средств доступа, поэтому все объекты должны быть единственными, которые могут знать о своих данных, и единственный способ воздействовать на них - через косвенные сообщения , т.е. методы.

Но даже в C # есть ключевое слово внутренней видимости, а у Java есть доступность на уровне пакета по умолчанию для некоторых вещей. На самом деле C ++ приближается к идеалу ООП, минимизируя компромисс видимости в классе, точно указав , какой другой класс и только другие классы могут его видеть.

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

Но есть еще атрибут InternalsVisibleTo (otherAssembly), который действует как механизм друзей перекрестной сборки . Microsoft использует это для сборок визуального конструктора .

-1
20.08.2008 07:46:14

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

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


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

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

3
27.08.2010 21:11:59

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

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

1
20.08.2008 09:19:44

Один конкретный пример, который я использую, 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;
}
1
20.08.2008 13:40:41
Это может быть дело вкуса, но я не думаю, что сохранение нескольких нажатий клавиш оправдывает использование друга здесь. GetMySingleton () должен быть статическим методом класса.
Gorpik 16.01.2009 08:25:06
Закрытый c-tor запрещает использование функции не-друга для создания экземпляра MySingleton, поэтому здесь необходимо ключевое слово friend.
JBRWilkinson 7.09.2009 09:59:51
@Gorpik " Это может быть дело вкуса, но я не думаю, что сохранение нескольких нажатий клавиш оправдывает использование друга здесь. " Это делает. Во всяком случае, friendэто не нужно особое «оправдание», при добавлении функции члена нет.
curiousguy 23.12.2011 07:02:23
Одиночки считается плохой практикой в любом случае (Google «синглтон вредна» , и вы получите много результатов , как это я не думаю , что с помощью функции для реализации антипаттерн можно считать хорошим использованием этой функции..
weberc2 10.03.2015 16:56:44

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

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

2
21.08.2008 13:03:12
« Что касается оператора << и оператора >>, то нет веских причин дружить с этими операторами ». Абсолютно правильно. « Это дает дополнительное преимущество, позволяя вам сделать эти функции виртуальными », если рассматриваемый класс предназначен для деривации, да. Иначе зачем?
curiousguy 23.12.2011 06:59:59
Я действительно не понимаю, почему этот ответ был отклонен дважды - и даже без объяснения причин! Это грубо.
curiousguy 23.12.2011 07:00:49
Виртуальная добавила бы хит производительности, который мог бы быть довольно большим в сериализации
paulm 21.04.2015 14:28:24

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

5
26.01.2016 16:55:28

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

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

161
12.02.2017 10:37:11
Это именно то, для чего я его использую. Это или просто установите переменные-члены для защищенных. Это просто позор, это не работает для C ++ / CLI :-(
Jon Cage 3.11.2011 16:51:13
Лично я бы препятствовал этому. Обычно вы тестируете интерфейс, т.е. дает ли набор входов ожидаемый набор выходных данных. Зачем вам нужно проверять внутренние данные?
Graeme 6.09.2012 22:04:28
@ Grame: Потому что хороший план тестирования включает в себя тестирование как белого, так и черного ящиков.
Ben Voigt 29.04.2013 00:30:46
Я склонен согласиться с @Graeme, как прекрасно объясняется в этом ответе .
Alexis Leclerc 9.10.2015 21:46:31
@ Грэм, это не может быть внутренняя информация напрямую. Я могу быть методом, который выполняет определенную операцию или задачу над этими данными, когда этот метод является закрытым для класса и не должен быть общедоступным, в то время как некоторым другим объектам может потребоваться передать или заполнить защищенный метод этого класса своими собственными данными.
Francis Cugler 12.02.2017 10:46:29

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

         Game
        /    \
 TwoPlayer  SinglePlayer

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

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

4
4.09.2008 23:43:46

Еще одна распространенная версия примера Эндрю, страшный код-куплет

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);
}

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

10
10.09.2008 02:52:54
Зачем кому-то нужен друг для этого? Почему бы не позволить addChildфункции-члену также установить родителя?
Nawaz 27.02.2012 06:01:50
Лучший пример будет сделать setParentдруг, так как вы не хотите , чтобы позволить клиентам изменить родитель , так как вы будете управлять им в addChild/ removeChildкатегории функций.
Ylisar 26.02.2014 15:23:43

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

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
12.12.2008 12:10:05
Разве интерфейсы не были бы более чистым способом достигнуть этого? Что мешает кому-то искать MyFooPrivate.h?
JBRWilkinson 7.09.2009 10:02:15
Ну, если вы используете частную и публичную тайну, вы легко победите. Под «сокрытием» я подразумеваю, что пользователю MyFoo на самом деле не нужно видеть приватных участников. Кроме того, полезно поддерживать совместимость с ABI. Если вы сделаете _private указатель, частная реализация может измениться сколько угодно, не затрагивая общедоступный интерфейс, тем самым сохраняя совместимость ABI.
shash 16.09.2009 07:12:54
Вы имеете в виду идиому PIMPL; цель, для которой не дополнительная инкапсуляция, как вы, похоже, говорите, а удаление деталей реализации из заголовка, чтобы изменение детализации не вызывало перекомпиляцию клиентского кода. Кроме того, нет необходимости использовать друга для реализации этой идиомы.
weberc2 10.03.2015 16:52:39
Ну да. Его основная цель - перенести детали реализации. Друг там полезен, чтобы обращаться с частными членами внутри публичного класса от частного или наоборот.
shash 11.03.2015 06:05:24

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

93
23.05.2017 12:18:25
Привет, я получаю синтаксическую ошибку (в xcode 4), когда я пробую ваш CRTP. Xcode считает, что я пытаюсь наследовать шаблон класса. Ошибка возникает P<C>в template<template<typename> class P> class C : P<C> {};заявлении «Использование шаблона класса C требует аргументов шаблона». Вы сталкивались с такими же проблемами или, возможно, знаете решение?
bennedich 22.10.2012 14:30:59
На первый взгляд @bennedich, это похоже на ошибку, которую вы получите при недостаточной поддержке функций C ++. Что довольно распространено среди компиляторов. Использование FlexibleClassвнутри FlexibleClassдолжно косвенно относиться к своему собственному типу.
Yakk - Adam Nevraumont 29.12.2012 17:44:54
@bennedich: правила использования имени шаблона класса в теле класса изменены в C ++ 11. Попробуйте включить режим C ++ 11 в вашем компиляторе.
Ben Voigt 29.04.2013 00:33:31
В Visual Studio 2015 добавьте эту общедоступную: f () {}; f (int_type t): значение (t) {}; Чтобы предотвратить эту ошибку компилятора: ошибка C2440: «<function-style-cast>»: невозможно преобразовать из «utils :: f :: int_type» в «utils :: f» примечание: ни один конструктор не может принять тип источника или конструктор разрешение перегрузки было неоднозначным
Damian 29.03.2016 07:06:04

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

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

4
16.01.2009 08:33:42
« операторы << и >> являются каноническим примером ». Нет. Скорее примеры канонического счетчика .
curiousguy 23.12.2011 06:57:27
@curiousguy: операторы <<и, >>как правило, друзья, а не участники, потому что делать из них членов будет неудобно. Конечно, я говорю о случае, когда этим операторам необходим доступ к частным данным; иначе дружба бесполезна.
Gorpik 2.01.2012 09:21:11
« потому что создание их членами сделало бы их неудобными для использования». Очевидно, что создание operator<<и operator>>члены класса значения вместо не членов или членов i|ostreamне будут обеспечивать желаемый синтаксис, и я не предлагаю его. « Я говорю о случае, когда этим операторам необходим доступ к частным данным ». Я не совсем понимаю, почему операторам ввода / вывода нужен доступ к закрытым членам.
curiousguy 18.02.2012 04:49:45

Я использую ключевое слово 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
7.09.2009 09:03:21

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 . Как это вообще имеет смысл? (подсказка: это не так)

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

16
7.09.2009 09:24:47

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

Из 2 :

 class Fred;

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

 class Fred : private virtual FredBase {
 public:
   ...
 }; 
3
7.09.2009 09:38:07

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

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

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

4
11.02.2010 14:42:53
@curiousguy: даже в случае с шаблонами это правда.
Nawaz 27.02.2012 06:09:29
@Nawaz Шаблон дружбы может быть предоставлен, но любой может создать новую частичную или явную специализацию без изменения класса предоставления дружбы. Но будьте осторожны с нарушениями ODR, когда вы делаете это. И не делай этого в любом случае.
curiousguy 5.03.2012 02:18:02

Дружественные функции и классы обеспечивают прямой доступ к закрытым и защищенным членам класса, чтобы избежать нарушения инкапсуляции в общем случае. Большая часть использования с 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
28.03.2012 14:08:56

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

3
29.12.2012 02:55:28

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

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

с 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
6.02.2014 09:50:38

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

8
19.02.2015 19:34:00
Но может ли публичная функция также использоваться для этого? В чем преимущество использования друзей?
Zheng Qu 15.07.2016 10:03:05
@Maverobot Не могли бы вы уточнить свой вопрос?
VladimirS 15.07.2016 17:12:41

В 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
25.03.2016 18:05:27

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

0
7.04.2016 20:01:40

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

Клуб Хаус

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 это улица с двусторонним движением, где другие классы участников ограничены.

1
12.02.2017 11:43:44

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

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

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

2
18.08.2017 03:53:02