|
|
В заключении можно обсудить, когда при обращении в закрытую часть
пользовательского типа стоит использовать функции-члены, а когда
функции-друзья. Некоторые функции, например конструкторы, деструкторы
и виртуальные функции ($$R.12), обязаны быть членами, но для других
есть возможность выбора. Поскольку, описывая функцию как член, мы
не вводим нового глобального имени, при отсутствии других доводов
следует использовать функции-члены.
Рассмотрим простой класс X:
class X { // ... X(int); int m1(); int m2() const; friend int f1(X&); friend int f2(const X&); friend int f3(X); };
Вначале укажем, что члены X::m1() и X::m2() можно вызывать только для объектов класса X. Преобразование X(int) не будет применяться к объекту, для которого вызваны X::m1() или X::m2():
void g()
{
1.m1(); // ошибка: X(1).m1() не используется
1.m2(); // ошибка: X(1).m2() не используется
}
Глобальная функция f1() имеет то же свойство ($$4.6.3), поскольку ее параметр - ссылка без спецификации const. С функциями f2() и f3() ситуация иная:
void h()
{
f1(1); // ошибка: f1(X(1)) не используется
f2(1); // нормально: f2(X(1));
f3(1); // нормально: f3(X(1));
}
Следовательно операция, изменяющая состояние объекта класса,
должна быть членом или глобальной функцией с параметром-ссылкой
без спецификации const. Операции над основными типами, которые
требуют в качестве операндов адреса (=, *, ++ и т.д.),
для пользовательских типов естественно определять как члены.
Обратно, если требуется неявное преобразование типа для всех
операндов некоторой операции, то реализующая ее функция должна
быть не членом, а глобальной функцией и иметь параметр типа ссылки
со спецификацией const или нессылочный параметр. Так обычно обстоит
дело с функциями, реализующими операции, которые для основных
типов не требуют адресов в качестве операндов (+, -, || и т.д.).
Если операции преобразования типа не определены, то нет
неопровержимых доводов в пользу функции-члена перед функцией-другом
с параметром-ссылкой и наоборот. Бывает, что программисту просто
одна форма записи вызова нравится больше, чем другая.
Например, многим для обозначения функции обращения матрицы m больше
нравится запись inv(m), чем m.inv(). Конечно, если функция
inv() обращает саму матрицу m, а не возвращает новую, обратную m,
матрицу, то inv() должна быть членом.
При всех прочих равных условиях лучше все-таки остановиться
на функции-члене. Можно привести такие доводы. Нельзя гарантировать,
что когда-нибудь не будет определена операция обращения. Нельзя во
всех случаях гарантировать, что будущие изменения не повлекут за
собой изменения в состоянии объекта. Запись вызова функции-члена
ясно показывает программисту, что объект может быть изменен, тогда
как запись с параметром-ссылкой далеко не столь очевидна. Далее,
выражения допустимые в функции-члене могут быть существенно
короче эквивалентных выражений в глобальной функции. Глобальная
функция должна использовать явно заданные параметры, а в
функции-члене можно неявно использовать указатель this. Наконец,
поскольку имена членов не являются глобальными именами, они обычно
оказываются короче, чем имен глобальных функций.
| |