пример - friend structure c++




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

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

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

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


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

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


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

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


Во-первых, (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 );
};

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

Несколько замечаний:

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


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


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

От 2 :

 class Fred;

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

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

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

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

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

Ключевое слово 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();
};

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


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

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


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

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

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

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


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

         Game
        /    \
 TwoPlayer  SinglePlayer

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

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


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

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


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

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

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

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


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


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

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

На самом деле, ответы на это часто задаются на C ++.


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

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


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

Клубный дом

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

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


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

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

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

Но тогда есть InternalsVisibleTo Attribute (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();
}

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

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





friend