C ++ другу или не другу

19

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

Мне инстинктивно не нравится их способность обходить безопасность, которую обеспечивают Encapsulation и скрытие данных, я прочитал несколько статей в Интернете, и некоторые люди думали, что это было хорошей идеей с некоторыми законными применениями.

Что бы сказал эксперт ООП о функциях друзей в C ++? Должен ли я просто скользить по нему или я должен узнать больше об этом?

Нихилу
источник
@all: Потрясающие ответы и комментарии, это такой отличный способ учиться, я бы ни за что не узнал о друзьях в таких подробностях в учебнике.
Нихил
1
см .: programmers.stackexchange.com/questions/99589/…
Мартин Йорк,

Ответы:

13

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

 double a;
 Vector v, w;
 w = v * a;

Мы можем сделать это с помощью функции-члена:

public class Vector {
 ...
 Vector operator*(double a);
}

Но мы также хотели бы написать:

w = a * v

Это требует бесплатной функции:

 Vector operator*(double a, Vector v)

friendКлючевое слово было добавлено в C ++ , чтобы поддержать это использование. Функция free является частью реализации класса Vector и должна быть объявлена ​​в том же заголовке и реализована в том же исходном файле.

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

Кевин Клайн
источник
3
«Это требует свободной функции». Нет, это не так inline Vector operator*(double a, Vector v) { return v*a; }. Каноническое решение по сути.
MSalters
1
@MSalters: Хороший вопрос. Я выбрал плохой пример. Я думаю, что ваша встроенная функция является бесплатной по определению, но объявление друга не требуется.
Кевин Клайн
4
@MSalters: это верно только в том случае, если * является коммутативным отношением к a и v (x). Если компоненты вектора являются общими (не обязательно скалярами), вы должны сохранить порядок операндов
Эмилио Гаравалья
Это довольно теоретически. Возможно, единственным распространенным некоммутативным случаем будет, inline Vector operator*(double a, Vector v) { return -v*a; }и это все еще не требует дружбы.
MSalters
16

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

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

DeadMG
источник
+1 для функций-друзей не отличается от функций-членов с точки зрения инкапсуляции. Это верно только для публичных функций-членов.
TheFogger
1
@TheFogger: Возможно, вы также можете friendиспользовать функцию, которая также является «частной», например, объявленной только в одном TU.
DeadMG
5

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

JK
источник
5

« Что бы сказал эксперт по ООП ... » Это в основном зависит от того, насколько он опытен в C ++, что - по своей собственной спецификации - не является (и не хочет быть) языком для пуриста.

ООП зилоты не используют С ++ (они предпочитают Smalltalk и любят Java).

Зелоты функционального программирования не используют C ++ (они предпочитают LISP и его наследников)

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

И с точки зрения «функциональности», между a.fn(b)и fn(a,b)нет никакой разницы (где fnдруг): вовлеченные стороны одинаковы. Проще говоря, один синтаксис может быть более подходящим, чем другой: если fn является коммутативным относительно aи b, fn(a,b)вероятно, более подходящим, чем тогда a.fn(b)(где взгляды имеют «особую роль», которой на самом деле это не так).

Эмилио Гаравалья
источник
1
«ООП фанатики», которые любят Java, не понимают ООП. Геттеры? Сеттеры? Нет простого синтаксиса для замыканий? Перефразируя Алана Кея, он не так представлял себе ООП.
Конрад Рудольф
@Konrad: ревнители - превосходно неограниченный набор. Всегда есть фанатик, более фанатичный, чем данный фанат.
Эмилио Гаравалья
Я должен сказать, что проголосовал, потому что мне действительно понравился последний абзац. Имеет много смысла.
Jullealgon
5

«Друг» нарушает инкапсуляцию?

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

fredoverflow
источник
2

C ++ FAQ является лаконичным:

Используйте члена, когда можете, и друга, когда это необходимо.

FAQ представляет один из наиболее полезных способов мышления о дружбе:

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

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

Gnawme
источник
0

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

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

скоро
источник
-2

Дружественные функции C ++ тесно связаны со следующей функциональностью:

  1. свободные функции
  2. статические функции
  3. функции друзей

Это означает, что они не имеют this-указатель и, следовательно, находятся за пределами класса / объекта. С другой стороны, они часто принимают параметры, которые делают их снова принадлежащими классу. Вот пример, поясняющий ссылку:

class B;
class A {
public:
    friend void f(A &a, B &b);
private:
    int m_a;
};
class B {
public:
   friend void f(A &a, B &b);
private:
   int m_b;
};
void f(A &a, B &b) { /* uses both A's and B's private data */ }

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

Использование дружественного механизма в c ++ требует программистов, которые имеют опыт программирования на c ++ около 10-15 лет, и поэтому изначально вам следует избегать этого. Это расширенная функция.

ТР1
источник
7
А ты 10-15 лет выводил как?
DeadMG
10-15 лет приходит со времени, когда это впервые становится действительно необходимым.
1
3
Таким образом, вы произвольно составили номер.
DeadMG
3
-1: «Тебе следует избегать этого». Каждая функция C ++ была создана для решения проблемы. Когда эта проблема возникает, используйте соответствующую функцию.
Кевин Клайн
Спасибо за -1. Была причина для этого комментария. Думаю, это просто сложная концепция, что не все функции подходят для начинающих.
tp1