Бьерн Страуструп - Язык программирования С++. Главы 5-8
Страница 67. Друзья и члены



7.12 Друзья и члены

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

 
« Предыдущая статья   Следующая статья »