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

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

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


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

Ответы [ 30 ]

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

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

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

На ответ;

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

Вы можете пойти дальше по этому простому примеру, рассмотрев более сложный класс, такой как Window. Вполне вероятно, что в 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 );
};
148 голосов
/ 30 августа 2008

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

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

88 голосов
/ 13 декабря 2008

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

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

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

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

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

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

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

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

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

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

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

10 голосов
/ 10 сентября 2008

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

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 голосов
/ 19 февраля 2015

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

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

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

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

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

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

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

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

5 голосов
/ 25 августа 2008

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

...