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

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

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


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

Ответ 1

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

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

В ответ;

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

Вы можете принять этот простой пример, рассмотрев более сложный класс, например 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 );
};

Ответ 2

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

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

Ответ 3

Ключевое слово friend имеет множество полезных применений. Вот два момента, которые мне сразу видны:

Определение друга

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

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

    public:
        // let 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 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();
};

В this вы найдете не надуманный пример. Другой код, использующий это, находится в этом ответе. База CRTP использует этот указатель для доступа к полям данных производного класса с использованием указателей-членов данных.

Ответ 4

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

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

Собственно, ответы на С++ уже на это отвечает.

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

Другая распространенная версия примера Эндрю, ужасный код-парад

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

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

Ответ 8

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

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

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

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

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

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

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

Ответ 12

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

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

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

Ответ 13

Чтобы делать TDD много раз, я использовал ключевое слово "friend" в С++.

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


Обновлено: я нашел этот ценный ответ о ключевом слове "friend" из сайт Bjarne Stroustrup.

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

Ответ 14

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

         Game
        /    \
 TwoPlayer  SinglePlayer

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

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

Ответ 15

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

Из 2:

 class Fred;

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

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

Ответ 16

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

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

Ответ 17

Я использую только ключевое слово 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 по крайней мере.

Ответ 18

Вы должны быть очень осторожны, когда/где вы используете ключевое слово 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!)
  • Дружба не взаимна.
  • Дружба не транзитивна.
  • Дружба не наследуется
  • Или, как С++ часто задаваемые вопросы: "Только потому, что я предоставляю вам доступ к дружбе, я не предоставляю детям доступ ко мне, автоматически не предоставляет вашим друзьям доступа ко мне и автоматически не предоставляет мне доступ к вам".

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

Ответ 19

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

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

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

Ответ 20

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

Нет, единственная дружеская дружба: `(

Ответ 21

Один конкретный экземпляр, в котором я использую 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;
}

Ответ 22

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

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

Ответ 23

В С++ ключевое слово "friend" полезно при перегрузке оператора и создании Bridge.

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

   1.Overload "operator <<()" function in "ostream" class.
   2.Overload "operator<<()" function in "Point" class.
Теперь первый вариант не подходит, потому что если нам нужно снова перегрузить этот оператор для какого-то другого класса, тогда мы должны снова внести изменения в класс "ostream".
Вот почему второй вариант - лучший вариант. Теперь компилятор может позвонить "operator <<()":
   1.Using ostream object cout.As: cout.operator&lt&lt(Pointobj) (form ostream class).
2.Call without an object.As: operator&lt&lt(cout, Pointobj) (from Point class).

Из-за того, что мы выполнили перегрузку в классе 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);

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

Ответ 24

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

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

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

Ответ 25

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

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

Ответ 26

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

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

из 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;
}

Ответ 27

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

Ответ 28

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

The ClubHouse

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

Таким образом, с VIPMember и ClubHouse это двухсторонняя улица доступа, где другие классы участников ограничены.

Ответ 29

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

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

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

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

Ответ 30

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

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-style, а не загромождал класс.

Аналогично, шаблон, который я видел очень часто, состоит в том, чтобы поместить все действительно частные члены класса в другой класс, который объявлен в заголовке, определенном в 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;
}

Легче скрыть вещи, которые нисходящий поток не должен видеть таким образом.