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

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

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


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

Ответы [ 30 ]

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

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

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

Как ссылка для декларация друга говорит:

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

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

1 голос
/ 25 марта 2016

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

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).<br/>   2.Call without an object.As: operator&lt&lt(cout, Pointobj) (from Point class).

Поскольку мы реализовали перегрузку в классе Point. Поэтому, чтобы вызвать эту функцию без объекта, мы должны добавить ключевое слово "friend", потому что мы можем вызвать функцию друга без объекта. Теперь объявление функции будет выглядеть так:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"

2.) Ключевое слово друга при создании моста:
Предположим, нам нужно создать функцию, в которой мы должны получить доступ к закрытому члену двух или более классов (обычно называемых «мостом»). Как это сделать:
Чтобы получить доступ к закрытому члену класса, он должен быть членом этого класса. Теперь для доступа к закрытому члену другого класса каждый класс должен объявить эту функцию как функцию друга. Например : Предположим, есть два класса A и B. Функция "funcBridge()" хочет получить доступ к закрытому члену обоих классов. Тогда оба класса должны объявить "funcBridge()" как:
friend return_type funcBridge(A &a_obj, B & b_obj);

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

0 голосов
/ 07 апреля 2016

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

0 голосов
/ 06 февраля 2014

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

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

из 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 голосов
/ 20 августа 2008

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

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

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

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

0 голосов
/ 12 февраля 2017

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

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

То есть с VIPMember и ClubHouse это улица с двусторонним движением, где другие классы участников ограничены.

0 голосов
/ 12 декабря 2008

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

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

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

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

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

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

...