Стоит ли переходить к функции-члену или функции-другу, когда функция должна изменять состояние объекта?

В книге Бьярна Страуструпа «Язык программирования C ++» автор представляет
класс Matrix, который должен реализовывать функцию inv (). В разделе 11.5.1 он говорит
о двух возможностях сделать это. Одним из них является создание функции-члена, а другим —
сделать функцию друга inv (). Затем к концу раздела 11.5.2, где он говорит о
делая выбор, использовать ли друга или функцию-член, он говорит:

Если inv () действительно инвертирует саму матрицу m, а не возвращает новую матрицу,
является инверсией m, он должен быть членом.

Почему это так? Не может функция друга изменить состояние Матрицы и вернуть
ссылка на эту матрицу? Это из-за возможности прохождения временной матрицы
когда мы вызываем функцию? ..

5

Решение

Честно говоря, я думаю, что единственными причинами для принятия такого решения являются синтаксическое удобство и традиции. Я объясню почему, показывая, в чем (не) различия между ними и как эти различия имеют значение при принятии решения.

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

9

Другие решения

Философия инкапсуляции, присущая OOD (которую C ++ пытается продвигать), диктует, что состояние объекта может быть изменено только изнутри.
Это синтаксически правильно (компилятор это позволяет), но его следует избегать.

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

0

Есть два противоположных аргумента относительно использования друзей:

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

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

Обе стороны могли бы поспорить, но я склонен согласиться с первым вариантом.

Что касается вашего вопроса, то он как PherricOxide упоминается в его комментарии: если внутренние атрибуты класса необходимо изменить, лучше, чем это сделать с помощью метода-члена, тем самым обеспечивая инкапсуляцию. Это соответствует первому варианту, упомянутому выше.

0
По вопросам рекламы [email protected]