В книге Бьярна Страуструпа «Язык программирования C ++» автор представляет
класс Matrix, который должен реализовывать функцию inv (). В разделе 11.5.1 он говорит
о двух возможностях сделать это. Одним из них является создание функции-члена, а другим —
сделать функцию друга inv (). Затем к концу раздела 11.5.2, где он говорит о
делая выбор, использовать ли друга или функцию-член, он говорит:
Если inv () действительно инвертирует саму матрицу m, а не возвращает новую матрицу,
является инверсией m, он должен быть членом.
Почему это так? Не может функция друга изменить состояние Матрицы и вернуть
ссылка на эту матрицу? Это из-за возможности прохождения временной матрицы
когда мы вызываем функцию? ..
Честно говоря, я думаю, что единственными причинами для принятия такого решения являются синтаксическое удобство и традиции. Я объясню почему, показывая, в чем (не) различия между ними и как эти различия имеют значение при принятии решения.
Какие различия существуют между функциями, не являющимися друзьями, и общими функциями? Немного. В конце концов, функция-член — это просто обычная функция со скрытым this
параметр и доступ к закрытым членам класса.
// what is the difference between the two inv functions?
// --- our code ---
struct matrix1x1 { // this one is simple :P
private:
double x;
public:
//... blah blah
void inv() { x = 1/x; }
friend void inv(matrix1x1& self) { self.x = 1/self.x; }
};
matrix1x1 a;
// --- client code ---
// pretty much just this:
a.inv();
// vs this:
inv(a);
void lets_try_to_break_encapsulation(matrix1x1& thingy) {
thingy.x = 42; // oops, error. Nope, still encapsulated.
}
Они оба предоставляют одинаковую функциональность и никоим образом не меняют возможности других функций. Те же самые внутренние объекты подвергаются воздействию внешнего мира: нет никакой разницы с точки зрения инкапсуляции. Нет абсолютно ничего, что другие функции могли бы делать по-другому, потому что есть функция друга, которая изменяет приватное состояние.
Фактически, можно написать большинство классов с большинством функций как функции, не являющиеся членами-друзьями (виртуальные функции и некоторые перегруженные операторы должны быть членами), обеспечивая одинаковую степень инкапсуляции: пользователи не могут писать никакие другие функции-друзья без изменения класса, и нет функции, кроме функций друзей, могут получить доступ к закрытым членам. Почему бы нам не сделать это? Потому что это противоречило бы стилю 99,99% программистов на C ++, и от этого нельзя было извлечь больших преимуществ.
Различия заключаются в характере функций и способах их вызова. Быть функцией-членом означает, что вы можете получить указатель на функцию-член из нее, а быть функцией, не являющейся членом, означает, что вы можете получить указатель на функцию. Но это редко актуально (особенно с такими обёртками std::function
вокруг).
Оставшаяся разница является синтаксической. Разработчики языка D решили просто объединить все это и сказать, что вы можете вызвать функцию-член напрямую, передав ей объект типа inv(a)
и вызвать свободную функцию в качестве члена своего первого аргумента, как a.inv()
, И ни один класс внезапно не стал плохо заключенным из-за этого или чего-то еще.1
Чтобы обратиться к конкретному примеру в вопросе, следует inv
быть членом или не членом? Я, вероятно, сделал бы это членом, для аргумента фамильярности, который я обрисовал выше. Не стилистически, это не имеет значения.
1. Это вряд ли произойдет в C ++, потому что на этом этапе это будет серьезное изменение, без существенной выгоды. Это, для крайнего примера, сломало бы matrix1x1
класс, который я написал выше, потому что это делает оба вызова неоднозначными.
Философия инкапсуляции, присущая OOD (которую C ++ пытается продвигать), диктует, что состояние объекта может быть изменено только изнутри.
Это синтаксически правильно (компилятор это позволяет), но его следует избегать.
Это подвержено ошибкам, позволяя элементам в системе изменять друг друга без использования предопределенных интерфейсов.
Хранение и функциональность объекта могут измениться, и поиск кода (который может быть огромным), который использует определенную часть объекта, был бы кошмаром.
Есть два противоположных аргумента относительно использования друзей:
Одна сторона говорит, что друзья уменьшают инкапсуляцию, потому что теперь вы позволяете внешним сущностям получать доступ к внутренним элементам класса, а внутренние должны быть изменены только методами-членами.
Другая сторона говорит, что друзья могут на самом деле увеличить инкапсуляцию, так как вы можете предоставить доступ к внутренним элементам класса небольшому набору внешних сущностей, что устраняет необходимость делать общедоступными внутренние атрибуты класса, чтобы эти внешние сущности могли получить доступ / манипулировать ими. их.
Обе стороны могли бы поспорить, но я склонен согласиться с первым вариантом.
Что касается вашего вопроса, то он как PherricOxide упоминается в его комментарии: если внутренние атрибуты класса необходимо изменить, лучше, чем это сделать с помощью метода-члена, тем самым обеспечивая инкапсуляцию. Это соответствует первому варианту, упомянутому выше.