Страница 21 из 48 Друзья класса Три спецификатора доступа обеспечивают в C++ управление доступом. Эти спецификаторы являются основанием принципа инкапсуляции - одного из трёх основных принципов объектно-ориентированного программирования. Соблюдение правил доступа повышает надёжность программного обеспечения. Спецификаторы доступа способны обеспечить многоуровневую защиту функций и данных в наследуемых классах. Порождаемые на основе "инкапсулированных" классов объекты способны поддерживать жёсткий интерфейс. Они подобны "чёрным" ящикам с чётко обозначенными входами и выходами. Вместе с тем, следует признать, что система управления доступом, реализованная на основе трёх спецификаторов, не является гибкой. С её помощью может быть реализована защита по принципу "допускать ВСЕХ (члены класса, объявленные в секции public) или не допускать НИКОГО (члены класса, объявленные в секциях protected и private)". В C++ существует возможность организации более гибкой защиты. Здесь можно также объявлять функции, отдельные функции-члены классов и даже классы (в этом случае речь идёт о полном множестве функций-членов класса), которые получают доступ к защищённым и приватным членам данного класса. Что означает реализацию системы управления доступом принципу "не допускать НИКОГО, КРОМЕ". Такие функции и классы называют дружественными функциями и классами. Объявление дружественных классов и функций включается в объявление данного класса вместе со спецификатором объявления friend. Здесь нам потребуется всего одна форма Бэкуса-Наура для того, чтобы дополнить синтаксис объявления. СпецификаторОбъявления ::= friend ::= *****
Рассмотрим небольшой пример использования дружественных функций и классов, а затем сформулируем основные правила работы с друзьями классов. В программе объявлены два класса, один из которых является другом другого класса и всеобщая дружественная функция. #include <iostream.h> class XXX; /* Неполное объявление класса. Оно необходимо для объявления типа параметра функции-члена для следующего класса. */ class MMM { private: int m1; public: MMM(int val); void TypeVal(char *ObjectName, XXX& ClassParam); }; MMM::MMM(int val) { m1 = val; } /* Определение функции-члена TypeVal располагается после объявления класса XXX. Только тогда транслятор узнаёт о структуре класса, к которому должна получить доступ функция MMM::TypeVal. */ class XXX { friend class YYY; friend void MMM::TypeVal(char *ObjectName, XXX& ClassParam); friend void TypeVal(XXX& ClassParamX, YYY& ClassParamY); /* В классе объявляются три друга данного класса: класс YYY, функция-член класса MMM, простая функция TypeVal. В класс XXX включаются лишь объявления дружественных функций и классов. Все определения располагаются в других местах - там, где им и положено быть - в своих собственных областях видимости. */ private: int x1; public: XXX(int val); }; XXX::XXX(int val) { x1 = val; } void MMM::TypeVal(char *ObjectName, XXX& ClassParam) { cout << "Значение " << ObjectName << ": " << ClassParam.x1 << endl; } /* Отложенное определение функции-члена MMM::TypeVal. */ class YYY { friend void TypeVal(XXX& ClassParamX, YYY& ClassParamY); private: int y1; public: YYY(int val); void TypeVal(char *ObjectName, XXX& ClassParam); }; YYY::YYY(int val) { y1 = val; } void YYY::TypeVal(char *ObjectName, XXX& ClassParam) { cout << "Значение " << ObjectName << ": " << ClassParam.x1 << endl; } void TypeVal(XXX& ClassParamX, YYY& ClassParamY); void main() { XXX mem1(1); XXX mem2(2); XXX mem3(3); YYY disp1(1); YYY disp2(2); MMM special(0); disp1.TypeVal("mem1", mem1); disp2.TypeVal("mem2", mem2); disp2.TypeVal("mem3", mem3); special.TypeVal("\n mem2 from special spy:", mem2); TypeVal(mem1, disp2); TypeVal(mem2, disp1); } void TypeVal(XXX& ClassParamX, YYY& ClassParamY) { cout << endl; cout << "???.x1 == " << ClassParamX.x1 << endl; cout << "???.y1 == " << ClassParamY.y1 << endl; }
В этом примере все функции имеют одинаковые имена. Это не страшно. Это даже полезно, поскольку становится очевидным факт существования разных областей действия имён. В заключение раздела перечислим основные правила пользования новыми средствами управления доступа - дружественной системой защиты. - Друзья класса не являются членами класса. Они должны определяться вне класса, для которого они объявляются друзьями, а об особых отношениях между ними и данным классом свидетельствует лишь специальное объявление(!) со спецификатором объявления friend. Объявления дружественного класса означает, что в дружественном классе доступны все компоненты объявляемого класса.
- Дружественные данному классу функции не являются членами этого класса. Поэтому они не могут быть вызваны из объекта-представителя класса, для которого была объявлена другом данная функция, при помощи операции доступа к члену класса.
- Дружественная функция может быть функцией-членом другого ранее объявленного класса. Правда, при этом само определение дружественной функции приходится располагать после объявления класса, другом которого была объявлена данная функция. Это не очень удобно и красиво, но зато работает.
- Дружественная функция не имеет this указателя для работы с классом, содержащим её объявление в качестве дружественной функции. Дружба - это всего лишь дополнение принципа инкапсуляции и ничего более.
- Дружественные отношения не наследуются. Дружественные функции не имеют доступа к членам производного класса, чьи базовые классы содержали объявления этих функций. Дети не отвечают за отношения своих родителей.
|