Дружественные классы С++
Приступая к изучению темы Дружественные классы, вам необходимо знать, что такое дружественные функции. Тогда вам намного легче будет все понять и мне не придется повторять то, что уже написано в статье о дружественных функциях. Говоря о дружественных классах, хочу отметить, что иногда удобней не объявлять дружественные функции в теле другого класса, а объявить вместо них дружественный класс. Тогда методы этого дружественного класса, автоматически станут дружественными классу, который предоставляет дружбу.
К примеру, если дружественный класс содержит 5 — 10 методов и каждому из них необходим доступ к приватным элементам другого класса. Тогда, объявив дружественный класс, сам код будет выглядеть компактней. Но если доступ к элементам другого класса необходим только нескольким методам дружественного класса, лучше воспользоваться объявлением дружественных функций. Так наши приватные элементы будут более защищены от случайного внесения в них ошибочных данных. И это действительно важно!
Рассмотрим следующий код программы. В нем мы создадим два класса. Один из них объявим дружественным и, используя его методы, внесем изменения в приватные элементы другого класса.
#include #include using namespace std; class child; //заранее объявляем класс, который станет дружественным class schoolchild //определяем следующий класс < char name[16]; char surname[16]; int clas; public: schoolchild (char*, char*, int);//конструктор void getData(); friend child;//указываем, что класс дружественный >; // определяем методы класса schoolchild schoolchild::schoolchild(char *n, char *s, int c) < strcpy(name, n); strcpy(surname, s); clas = c; >void schoolchild::getData() < cout class child //определяем дружественный класс < public: void changeClas(schoolchild &, int ); void getChangeData(schoolchild); >; // определяем методы класса child void child::changeClas(schoolchild &obj, int newCl) //передаем объект класса и вносим изменения в int clas < obj.clas = newCl; >void child::getChangeData(schoolchild obj) < cout int main() < setlocale(LC_ALL, "rus"); //создаем объекты класса schoolchild schoolchild visotscaya ( "Маргарита", "Высоцкая", 3); schoolchild semenov ( "Александр", "Семенов", 3); cout << "Список учеников 3-го класса:\n"; visotscaya.getData(); semenov.getData(); child transfer; //создаем объект transfer - перевод в с следующий класс transfer.changeClas(visotscaya, 4); transfer.changeClas(semenov, 4); cout
Класс, который будет дружественным, надо объявить до того, как мы укажем в другом классе, что он является дружественным. Так делается, если само определение дружественного класса описано ниже определения класса, который предоставляет «дружбу». В строке 6 объявляем класс child так как определим его уже ниже в коде, после того как укажем, что он является дружественным классу schoolchild . Далее определяем класс schoolchild — строки 8-17. В поле private этого класса размещены две строки name[16] , surname[16] и переменная clas , которые будут инициализированы сразу при создании объекта в главной функции main , с помощью конструктора класса, объявленного в строке 14 . В строке 16 указываем, что класс child будет дружественным классу schoolchild — friend child; . В определении конструктора класса, используя функцию strcpy , заполняем значениями строки, а так же инициализируем переменную clas — строки 19 — 24. Ниже определяем метод класса, который будет выводить все данные на экран — void schoolchild::getData() .
Строки 31 — 46 — определение класса child и его методов. Как было видно, в классе schoolchild явно не указано, что методы класса child являются дружественными. В нем дружественным объявлен сам класс child , при этом все его методы автоматически становятся дружественными классу schoolchild . Поэтому, используя методы дружественного класса child , мы можем обращаться к элементам класса schoolchild , даже к приватным, и изменять их значения. Класс child , для простоты восприятия, содержит всего два метода. Определены они в строках 38 — 46.
Метод void changeClas(schoolchild &, int ); принимает в виде параметров адрес объекта класса schoolchild (для того чтобы внести изменения в его приватный элемент) и значение типа int (то значение, которое надо записать в элемент clas ). Второй метод, void getChangeData(schoolchild); , просто выводит на экран информацию с измененными данными. Строки 53 — 54 — создаем два объекта класса schoolchild и в скобках задаем значения его элементам. Ниже выводим эти данные на экран. В строке 60 объявляем объект дружественного класса — child transfer; и через методы класса child обращаемся к объектам и элементам класса schoolchild . В подтверждение того, что нам удалось сохранить измененные данные в объектах класса schoolchild , мы снова выводим данные на экран используя метод getData(); — строки 70 — 71.
Вот, собственно, результат работы нашей программы:
CppStudio.com
Список учеников 3-го класса:
Маргарита Высоцкая 3-й класс
Александр Семенов 3-й класс
Перевод в следующий класс:
Маргарита Высоцкая переведен(а) в 4-й класс
Александр Семенов переведен(а) в 4-й класс
Список учеников 4-го класса:
Маргарита Высоцкая 4-й класс
Александр Семенов 4-й класс
Для продолжения нажмите любую клавишу . . .
Все, что было задумано, осуществилось — данные приватных элементов изменены и сохранены с помощью методов дружественного класса.
Основная информация, которую необходимо запомнить:
- чтобы объявить класс дружественным, в теле класса, который предоставляет дружбу, перед именем дружественного класса необходимо использовать зарезервированное слово С++ — friend ;
- как и в случае с дружественными функциями, нет разницы, в каком поле класса мы объявим дружественный класс — private , public или protected . Мы все равно сможем обращаться к его методам из главной функции main() ;
- если определение дружественного класса располагается ниже определения класса, предоставляющего дружбу, то объявить дружественный класс надо выше. Это поможет избежать ошибок при компиляции;
- когда класс объявлен дружественным, все его методы так же становятся дружественными, к тому классу в котором он объявлен. При этом методы класса, который разрешил дружбу не имеют доступа к элементам дружественного класса;
- не следует злоупотреблять и объявлять множество дружественных классов. Помните о том, что мы должны заботься о защите данных. Иногда целесообразней использовать дружественные функции;
Пожалуй, этой информации будет достаточно, чтобы продолжить работу и ваши эксперименты с дружественными классами. Если у вас остались вопросы — задавайте их нам.
К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!
Зачем нужны друзья класса c
Дружественные функции — это функции, которые не являются членами класса, однако имеют доступ к его закрытым членам — переменным и функциям, которые имеют спецификатор private.
Для определения дружественных функций используется ключевое слово friend . Например, определим следующую программу:
#include class Auto < friend void drive(const Auto&); friend void setPrice(Auto&, unsigned); public: Auto(std::string autoName, unsigned autoPrice) < name = autoName; price = autoPrice; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void drive(const Auto &car) < std::cout void setPrice(Auto &car, unsigned price) < car.price = price; >int main() < Auto tesla("Tesla", 5000); tesla.print(); // drive(tesla); setPrice(tesla, 8000); tesla.print(); // >
Здесь определен класс Auto, который представляет автомобиль. У этого класса определены приватные закрытые переменные name (название автомобиля) и price (цена автомобиля). Также в классе объявлены две дружественные функции: drive (функция вождения автомобиля) и setPrice (функция назначения цены). Обе этих функции принимают в качестве параметра ссылку на объект Auto.
Когда мы объявляем дружественные функции, то фактически мы говорим компилятору, что это друзья класса и они имеют доступ ко всем членам этого класса, в том числе закрытым.
При этом для дружественных функций не важно, определяются они под спецификатором public или private. Для них это не имеет значения.
Определение этих функций производится вне класса. И поскольку эти функции являются дружественными, то внутри этих функций мы можем через переданную ссылку Auto обратиться ко всем его закрытым переменным.
Консольный вывод программы:
Tesla : 5000 Tesla is driven Tesla : 8000
Определение дружественных функций в классе
Дружественные функции могут определяться в другом классе. Например, определим класс Person, который использует объект Auto:
#include class Auto; // объявление класса Auto, чтобы Person видел этот класс class Person < public: Person(std::string p_name) < name = p_name; >void drive(const Auto&); void setPrice(Auto&, unsigned); private: std::string name; >; class Auto < // объявление дружественных функций friend void Person::drive(const Auto&); friend void Person::setPrice(Auto&, unsigned); public: Auto(std::string a_name, unsigned a_price) < name = a_name; price = a_price; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void Person::drive(const Auto &car) < std::cout void Person::setPrice(Auto &car, unsigned price) < car.price = price; >int main() < Auto tesla; Person tom; tom.drive(tesla); tom.setPrice(tesla, 8000); tesla.print(); >
Вначале определен класс Person, который представляет человека. Однако поскольку класс Person использует класс Auto, то перед классом Person идет объявление класса Auto.
Две функции из класса Person принимают ссылку на объект Auto:
void drive(const Auto&); void setPrice(Auto&, unsigned);
То есть фигурально говоря, человек водит автомобиль и назначает ему цену с помощью этих функциий.
Класс Auto определяет дружественные функции с той же сигнатурой:
friend void Person::drive(Auto&); friend void Person::setPrice(Auto&, unsigned);
Причем поскольку данные функции будут определены в классе Person, то названия этих функций предваряются префиксом «Person::».
И поскольку в этих функциях предполагается использовать объект Auto, то ко времени определения этих функций все члены объекта Auto должны быть известны, поэтому определения функций находятся не в самом классе Person, а после класса Auto. И так как эти функции определены в классе Auto как дружественные, мы можем обратиться в этих функциях к закрытым членам класса Auto.
Консольный вывод программы:
Tom drives Tesla Tesla : 8000
Дружественные классы
В случае выше класс Person использует только две функции из класса Auto. Но допустим впоследствии возникла необходимость добавить в класс Auto еще ряд дружественных функций, которые будут определены в классе Person. Либо мы можем предполагать, что класс Person будет активно использовать объекты Auto. И в этом случае целесообразно определять не отдельные дружественные функции, а определить дружественным весь класс Person:
#include class Auto; // объявление класса Auto, чтобы Person видел этот класс class Person < public: Person(std::string p_name) < name = p_name; >void drive(const Auto&); void setPrice(Auto&, unsigned); private: std::string name; >; class Auto < // объявление дружественного класса friend class Person; public: Auto(std::string a_name, unsigned a_price) < name = a_name; price = a_price; >void print() < std::cout private: std::string name; // название автомобиля unsigned price; // цена автомобиля >; void Person::drive(const Auto& car) < std::cout void Person::setPrice(Auto& car, unsigned price) < car.price = price; >int main() < Auto tesla; Person tom; tom.drive(tesla); tom.setPrice(tesla, 8000); tesla.print(); >
Единственное, что в данном случае изменилось по сравнению с предыдущим примером — это то, что в классе Auto определение дружественных функций было заменено определением дружественного класса:
friend class Person;
То есть тем самым мы опять же говорим, что класс Person — это друг класса Auto, поэтому объекты Person могут обращаться к приватным переменным класса Auto. После этого в классе Person можно обращаться к закрытым членам класса Auto из любых функций.
Дружественные функции С++
Самым важным, но и, скорее всего, самым непонятным для вас сейчас станет определение дружественной функции. Дружественная функция — это функция, которая не является членом класса, но имеет доступ к членам класса, объявленным в полях private или protected . Долго не вникайте в суть этого определения, а лучше сразу переходите к следующему абзацу. Обещаю, что после прочтения статьи вы вернетесь к этому определению и вас посетит мысль: «Ну да — так и есть! Тут все понятно!»
Приступим к основному занятию программиста — практике! В примере запрограммируем следующее: создадим класс Woman25 , который, используя дружественные функции и обычные методы класса, будет получать данные об объекте (имя и вес) и выводить их на экран. Методы и friend -функции будут выполнять аналогичные действия. В этом и есть особенная польза данного примера — вы сможете посмотреть отличия в объявлении и определении дружественных функций от обычных методов класса. На основании полученных данных, программа даст пользователю совет относительно корректировки его веса. Ну что-же, лучше один раз увидеть…
#include #include using namespace std; class Woman25 < private: char *name;//имя int weight;//вес friend void setData(char *, int, Woman25&);//объявление дружественных функций friend void getData(Woman25&); public: Woman25()//конструктор < name = new char [20]; strcpy(name, "Норма"); weight = 60; >~Woman25()//деструктор < delete [] name; cout void setData(char*, int);//объявление методов класса void getData(); void advise(); >; void setData(char *n, int w, Woman25& object)//определяем friend-функцию setData < strcpy(object.name, n);//////////// object.weight = w; >void getData(Woman25& object)//определяем friend-функцию getData < cout void Woman25::setData(char *n, int w)//определяем set-метод класса < strcpy(name, n); weight = w; >void Woman25::getData()//определяем get-метод класса < cout void Woman25::advise()//определяем метод класса Совет (advise) < if(weight < 55)< //если вес меньше 55 кг cout else if(weight >= 55 && weight else < //если вес >65 кг cout > int main() < setlocale(LC_ALL, "rus"); Woman25 Norm; //создаем объект Norm, сработает конструктор и weight будет = 60, name - Норма Norm.getData(); //вызов метода класса cout
Комментарии к исходному коду. В строках 6 — 30 создаем класс Woman25 . Он содержит два приватных элемента класса: char *name; и int weight; . В private также объявим дружественные функции friend void setData(char *, int, Woman25&); и friend void getData(Woman25&); . Хотя их можно объявлять и определять в любом поле класса, будь то private , public или protected , но об этом поговорим позже. Так мы сделали, чтобы показать, что несмотря на объявление дружественных функций в поле private , к ним можно обращаться напрямую из функции main() . Тогда, когда методы класса должны располагаться в поле public всегда , если мы собираемся вызывать их вне класса . Объявление дружественных функций отличается от объявления методов класса еще и тем, что перед типом возвращаемого функцией значения используется зарезервированное слово friend . Обратите внимание, что в виде параметра мы передаем этим функциям ссылку на объект нашего класса — Woman25& . В обычных методах этого не будет. В поле public расположился конструктор класса — строки 15 — 20 . В нем мы задаем значение нормального веса — 60 кг, а так же, с помощью функции strcpy(name, "Норма"); , вносим данные в элемент класса name . Затем в строках 21 — 25 определяем деструктор класса ~Woman25() . Его задача, освободить, выделенную конструктором, динамическую память. А для того, чтобы убедиться, что он сработал и удалил динамическую память всех созданных объектов класса при завершении работы программы, мы добавили в него строку cout Строки 27 — 29 — объявление обычных методов класса.
Теперь начинается, пожалуй, самое интересное — определение вне класса наших дружественных функций и обычных методов класса. Все это располагается в строках 32 — 66. Смотрите, когда мы определяем дружественные функции , строки 32 — 36 и 38 — 41, мы не используем оператор :: двойное двоеточие (область видимости метода). Это уже говорит нам о том, что дружественная функция не принадлежит классу, не является его компонентом. А при определении остальных методов использование оператора :: является обязательным. Метод класса advise() , на основании полученных данных о весе, дает пользователю один из советов либо сообщает, что вес в норме.
Переходим в главную функцию — строки 68 — 87. Тут мы создаем объект класса Woman25 Norm; , при создании которого сработает конструктор и инициализирует элементы name и weight . Вызываем метод класса Norm.getData(); чтобы вывести на экран значение нормы. Со вторым созданным объектом Woman25 Anna; работаем, вызывая обычные set и get — методы класса, а с третьим объектом Woman25 Inna; — вызывая дружественные функции. Как видите, вызываются они как функции, которые не принадлежат классу. Объект класса мы передаем, как параметр.
Запускаем программу и видим следующее.
CppStudio.com
. Деструктор .
. Деструктор .
. Деструктор .
Для продолжения нажмите любую клавишу . . .
И friend-функции, и методы класса справились со своими задачами, а мы с вами увидели отличия в их объявлении и определении. Так же мы увидели, что деструктор класса, как положено, сработал 3 раза (т.е. при уничтожении каждого созданного объекта). Если у вас возникает вопрос «Собственно, а зачем все это, если методы класса и так прекрасно работают?» — это хорошо! Ответ такой — «Представьте, что у нас есть еще десяток классов. Например Girl6_7 , Girl8_9 , Man25 и т.д., Используя дружественные функции, нам не придется для каждого класса определять set и get -методы. Это в нашей программе они короткие! А если бы они занимали 20 — 30 строк? А так достаточно определить метод в одном из классов или вообще определить функцию, как глобальную, а в остальные классы прописать ее прототип, как дружественной функции (используя слово friend ). Мы экономим массу времени и наш код становится намного короче.»
Теперь, как всегда, немного теории, для закрепления материала:
- Дружественная функция может располагаться в любом поле класса – private , public или protected . Она при любых обстоятельствах будет иметь доступ к private -элементам класса и, даже если она сама находится в поле private (как в нашем примере), к ней можно будет обратиться вне класса, не используя специальных методов.
- Когда мы определяем дружественную функцию, элементы класса необходимо явно передавать в нее в виде параметров функции. Так как она не является компонентом класса, она не получает указатель this .
- В виде параметра, в дружественную функцию так же надо передать указатель или ссылку на объект класса. Иначе она не увидит данные какого класса ей принять и обработать.
- Функция может использоваться, как дружественная к нескольким классам.
- Вызываются дружественные функции, как обычные функции. Т.е не используется такой способ — Объект_класса.функция() . После внесения всех необходимых параметров в нее при вызове, она сама увидит с элементами какого класса и объекта надо работать.
Вот и все для начала. Вопросы, предложения и замечания по теме ждем в комментариях к этой статье.
К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!
Друзья / FAQ C++
Это то, что позволяет вашему классу предоставлять доступ к себе другому классу или функции.
Друзья могут быть функциями или другими классами. Класс предоставляет своим друзьям права доступа. Обычно разработчик имеет политический и технический контроль и над друзьями, и над функциями-членами класса (в противном случае вам может потребоваться разрешение от владельца других частей, если вы хотите обновить свой собственный класс).
Нарушают ли друзья инкапсуляцию?
Нет! При правильном использовании они улучшают инкапсуляцию.
«Друг» – это явный механизм предоставления доступа, как и членство. Вы не можете (в стандартной программе) предоставить себе доступ к классу без изменения его источника. Например:
class X < int i; public: void m(); // предоставляет доступ X::m() friend void f(X&); // предоставляет доступ f(X&) // . >; void X::m() < i++; /* X::m() может получить доступ к X::i */ >void f(X& x) < x.i++; /* f(X&) может получить доступ к X::i */ >
Описание модели защиты C++ смотрите в D&E (раздел 2.10) и TC++PL (разделы 11.5, 15.3 и C.11).
Часто бывает необходимо разделить класс пополам, если у этих двух половин будет разное количество экземпляров или разное время жизни. В этих случаях двум половинкам обычно требуется прямой доступ друг к другу (две половины раньше находились в одном классе, поэтому вы не увеличили объем кода, которому требуется прямой доступ к структуре данных; вы просто перетасовали код на два класса вместо одного). Самый безопасный способ реализовать это – подружить эти две половинки.
Если вы используете друзей, только как описано выше, то, что было private , так и останется private . Люди, которые этого не понимают, часто прилагают наивные усилия, чтобы избежать использования дружбы в ситуациях, подобных описанным выше, и часто фактически разрушают инкапсуляцию. Они либо используют публичные данные (абсурд!), либо делают данные доступными между этими половинами через публичные функции-члены get() и set() . Наличие публичных функций-членов get() и set() для частных данных – это нормально, только когда эти частные данные «имеют смысл» извне класса (с точки зрения пользователя). Во многих случаях эти функции-члены get() / set() почти так же плохи, как и общедоступные данные: они скрывают (только) имя частных данных, но не скрывают само существование частных данных.
Точно так же, если вы используете дружественные функции как синтаксический вариант функций, доступных в секции public класса, они не нарушают инкапсуляцию больше, чем ее нарушает функция-член. Другими словами, друзья класса не нарушают барьер инкапсуляции: вместе с функциями-членами класса они являются барьером инкапсуляции.
(Многие люди думают о дружественной функции как о чем-то вне класса. Вместо этого попробуйте думать о дружественной функции как о части открытого интерфейса класса. Дружественная функция в объявлении класса нарушает инкапсуляцию не больше, чем ее нарушает публичная функция-член: обе имеют одинаковые права доступа к закрытым частям класса.)
Какие преимущества/недостатки есть у использования дружественных функций?
Они предоставляют некоторую свободу в вариантах проектирования интерфейса.
Функции-члены и дружественные функции имеют одинаковые привилегии (наделены ими на 100%). Основное отличие состоит в том, что дружественная функция вызывается как f(x) , а функция-член – как x.f() . Таким образом, возможность выбора между функциями-членами ( x.f() ) и дружественными функциями ( f(x) ) позволяет разработчику выбирать синтаксис, который считается наиболее читаемым, что снижает затраты на поддержку.
Основным недостатком дружественных функций является то, что они требуют дополнительной строки кода, когда вам нужна динамическая привязка. Чтобы получить эффект virtual friend , дружественная функция должна вызывать скрытую (обычно защищенную) виртуальную функцию-член. Это называется идиомой виртуальной дружественной функции (Virtual Friend Function Idiom). Например:
class Base < public: friend void f(Base& b); // . protected: virtual void do_f(); // . >; inline void f(Base& b) < b.do_f(); >class Derived : public Base < public: // . protected: virtual void do_f(); // "Переопределить" поведение f(Base& b) // . >; void userCode(Base& b)
Что значит «дружба не передается по наследству, не является переходящей или взаимной»?
Тот факт, что я предоставляю вам дружественный доступ ко мне, не дает автоматически доступ ко мне вашим детям, не предоставляет автоматически доступ ко мне вашим друзьям и не предоставляет мне автоматически доступ к вам.
- Я не обязательно доверяю детям своих друзей. Привилегии дружбы не передаются по наследству. Производные дружественные классы – не обязательно друзья. Если класс Fred объявляет, что класс Base является другом, классы, производные от Base , не имеют никаких автоматических специальных прав доступа к объектам Fred .
- Я не обязательно доверяю друзьям своих друзей. Привилегии дружбы не передаваемы. Друг друга – не обязательно друг. Если класс Fred объявляет своим другом класс Wilma , а класс Wilma объявляет своим другом класс Betty , класс Betty не обязательно имеет какие-либо особые права доступа к объектам Fred .
- Вы не обязательно доверяете мне только потому, что я объявляю вас своим другом. Привилегии дружбы не взаимны. Если класс Fred объявляет, что класс Wilma является ему другом, объекты Wilma имеют особый доступ к объектам Fred , но объекты Fred не имеют автоматически особого доступа к объектам Wilma .
Что мне лучше объявлять в своем классе, функцию-член или дружественную функцию?
Используйте функцию-член, когда можете, и дружественную функцию, когда вам нужно.
Иногда друзья синтаксически лучше (например, в классе Fred дружественные функции позволяют параметру Fred быть вторым, в то время как функции-члены требуют, чтобы он был первым). Еще одно хорошее применение дружественных функций – это двоичные инфиксные арифметические операторы. Например, aComplex + aComplex должен быть определен как друг, а не как член, если вы хотите разрешить aFloat + aComplex (функции-члены не позволяют продвигать левый аргумент, так как это изменит класс объекта, который является получателем вызова функции-члена).
В остальных случаях выбирайте функцию-член вместо дружественной функции.