Вопрос

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

Каков хороший пример использования friend?


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

Это было полезно?

Решение

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

Существуют альтернативы спецификатору 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 );
};

Другие советы

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

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

Тот Самый friend ключевое слово имеет ряд полезных применений.Вот два варианта использования, которые сразу бросаются мне в глаза:

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

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

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

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

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

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

Частный базовый класс CRTP

Иногда вы обнаруживаете, что политике необходим доступ к производному классу:

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

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

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

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

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

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

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

На самом деле, часто задаваемые вопросы по C ++ ответы на это уже есть.

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

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

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

Редактировать:Прочитав 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, в котором единственными функциями, способными обращаться к закрытым членам, являются члены класса", хотя это полная бессмыслица по нескольким причинам.

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

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

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

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

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

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

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

Вместо того чтобы беспокоиться о том, всегда ли обе строки выполняются вместе и в согласованном порядке, вы могли бы сделать методы закрытыми и иметь функцию friend для обеспечения согласованности:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Это более чем ясно...

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

         Game
        /    \
 TwoPlayer  SinglePlayer

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

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

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

От 2:

 class Fred;

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

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

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

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


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

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

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

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

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

Однако я не использую ключевое слово in непосредственно в объявлениях классов, вместо этого я использую отличный шаблон для достижения этой цели:

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.

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

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

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

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

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

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

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

Один конкретный случай, когда я использую friend находится при создании Синглтон классы.Тот Самый 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;
}

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

Point p;
cout << p;

Однако для этого может потребоваться доступ к личным данным Point, поэтому мы определяем перегруженного оператора

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

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

Если вы рассматриваете friend как расширение класса, то с логической точки зрения это не проблема.Но, в таком случае, зачем было вообще выделять друга?

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

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

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

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

1.) Ключевое слово Friend при перегрузке оператора :
Примером перегрузки оператора является:Допустим, у нас есть класс "Point", который имеет две переменные с плавающей запятой
"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<<(Pointobj) (form ostream class).
2.Call without an object.As: operator<<(cout, Pointobj) (from Point class).

Потому что мы реализовали перегрузку в классе Point.Итак, чтобы вызвать эту функцию без объекта, мы должны добавить"friend" ключевое слово, потому что мы можем вызвать функцию 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.

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

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

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

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

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

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

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

От 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;
}

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

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

Здание клуба

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

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

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

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

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

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

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

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

Что это позволяет, так это то, что friend определяется исключительно в 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;
}

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top