Сведения о вопросе

Математик

01:53, 15th August, 2020

Теги

c++   oop   encapsulation   friend    

Когда следует использовать 'friend' в C++?

Просмотров: 606   Ответов: 25

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

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


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



  Сведения об ответе

SSESION

23:07, 22nd August, 2020

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

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

К ответу на этот вопрос;

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

Вы можете продолжить этот простой пример, рассмотрев более сложный класс, такой как окно. Вполне вероятно, что окно будет иметь много элементов функций / данных, которые не должны быть общедоступными, но ARE необходимы для связанного класса, такого как 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 );
};


  Сведения об ответе

ASER

13:28, 2nd August, 2020

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

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


  Сведения об ответе

SILA

04:55, 2nd August, 2020

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


  Сведения об ответе

fo_I_K

20:17, 25th August, 2020

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

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

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


  Сведения об ответе

lourence

13:21, 28th August, 2020

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

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

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


  Сведения об ответе

KOMP

09:31, 19th August, 2020

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, единственная функция, которая имеет доступ, - это не "какая-то случайная, неизвестная функция", а "the function listed in the class definition". Точно так же, как c2, мы можем увидеть, просто взглянув на определения классов, полный список тех, кто имеет доступ.

Так как же именно это менее инкапсулировано? Такое же количество кода имеет доступ к закрытым членам класса. И все , кто имеет доступ, перечислены в определении класса.

friend не нарушает инкапсуляцию. Это заставляет некоторых программистов Java чувствовать себя некомфортно, потому что когда они говорят "OOP", они на самом деле имеют в виду "Java". Когда они говорят "Encapsulation", они не имеют в виду "частные члены должны быть защищены от произвольного доступа", но "класс Java, где единственные функции, способные получить доступ к частным членам, являются членами класса", хотя это полная чушь по нескольким причинам .

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

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

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

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

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


  Сведения об ответе

ITSME

05:44, 3rd August, 2020

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

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

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


  Сведения об ответе

baggs

06:09, 3rd August, 2020

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

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

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

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

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

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


  Сведения об ответе

+-*/

19:41, 16th August, 2020

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


  Сведения об ответе

ASER

23:23, 3rd August, 2020

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


  Сведения об ответе

park

16:43, 14th August, 2020

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

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

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


  Сведения об ответе

DO__IT

03:46, 4th August, 2020

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

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


  Сведения об ответе

KOMP

08:40, 28th August, 2020

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

От 2-х :

 class Fred;

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

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


  Сведения об ответе

KOMP

20:04, 28th August, 2020

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

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


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

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


  Сведения об ответе

darknet

19:55, 12th August, 2020

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

         Game
        /    \
 TwoPlayer  SinglePlayer

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

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


  Сведения об ответе

COOL

03:02, 14th August, 2020

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


  Сведения об ответе

SKY

09:19, 2nd August, 2020

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

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


  Сведения об ответе

lool

17:12, 13th August, 2020

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

Предположим, вы хотите сравнить два объекта, чтобы увидеть, равны ли они. Вы можете либо:

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

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

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

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

Метод equal(Beer, Beer) теперь имеет прямой доступ к закрытым членам a и b (которые могут быть char *brand, float percentAlcohol и т. д. Это довольно надуманный пример , вы бы скорее применили friend к перегруженному == operator, но мы дойдем до этого.

Несколько вещей, чтобы отметить:

  • В friend является NOT функцию-член класса
  • Это обычная функция со специальным доступом к закрытым членам класса
  • Не заменяйте все аксессоры и мутаторы друзьями (вы также можете сделать все public !)
  • Дружба не бывает взаимной
  • Дружба не является преходящей
  • Дружба не передается по наследству
  • Или, как объясняет C++ FAQ: "только потому, что я даю вам дружеский доступ ко мне, это не автоматически дает вашим детям доступ ко мне, не автоматически дает вашим друзьям доступ ко мне и не автоматически дает мне доступ к вам."

Я действительно использую friends только тогда, когда это гораздо сложнее сделать по-другому. В качестве другого примера можно привести то, что многие функции векторной математики часто создаются как friends из-за интероперабельности Mat2x2 , Mat3x3 , Mat4x4 , Vec2 , Vec3 , Vec4 , и т.д. И это просто намного проще, чтобы быть друзьями, а не использовать аксессоры везде. Как уже отмечалось, friend часто бывает полезен, когда применяется к << (очень удобно для отладки), >> и, возможно, оператору == , но также может использоваться для чего-то подобного:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

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


  Сведения об ответе

prince

09:30, 12th August, 2020

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

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

Ну, хорошо, откровенно говоря, вы можете жить и без этого.


  Сведения об ответе

SSESION

14:19, 21st August, 2020

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

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


  Сведения об ответе

SSESION

11:12, 10th August, 2020

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


  Сведения об ответе

prince

20:39, 22nd August, 2020

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

Point p;
cout << p;

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

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

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

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

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

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

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


  Сведения об ответе

PHPH

16:19, 8th August, 2020

В 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).

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

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

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









  Сведения об ответе

SEEYOU

20:16, 4th August, 2020

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

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

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


  Сведения об ответе

lesha

10:25, 28th August, 2020

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

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


Ответить на вопрос

Чтобы ответить на вопрос вам нужно войти в систему или зарегистрироваться