Дружественные функции и классы
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 32 из 104.
Иногда функции или классу вне вашего класса требуется доступ к его закрытым членам. Вместо того чтобы делать эти члены открытыми, C++ предоставляет ключевое слово friend для предоставления выборочного доступа при сохранении инкапсуляции.
Дружественная функция объявляется внутри класса, но не является членом этого класса. Она может обращаться к private и protected членам класса:
class Box {
double width;
public:
Box(double w) : width(w) {}
// Объявление printWidth дружественной функцией
friend void printWidth(const Box& b);
};
// Определение дружественной функции — НЕ является функцией-членом
void printWidth(const Box& b) {
std::cout << b.width; // Может обращаться к приватному члену
}Обратите внимание, что printWidth определена вне класса без оператора разрешения области видимости. Это обычная функция, которая имеет особые привилегии доступа.
Вы также можете сделать весь класс дружественным, предоставляя всем его функциям-членам доступ к закрытым (private) членам:
class Engine {
int horsepower;
friend class Mechanic; // Mechanic может обращаться к приватным членам
public:
Engine(int hp) : horsepower(hp) {}
};
class Mechanic {
public:
void diagnose(const Engine& e) {
std::cout << e.horsepower; // Разрешено — Mechanic является дружественным классом
}
};Дружба не является взаимной — если Engine объявляет Mechanic другом, Mechanic может обращаться к закрытым членам Engine, но не наоборот. Используйте дружественные отношения умеренно, так как они создают сильную связанность между классами. Они наиболее полезны для перегрузки операторов и тесно связанных вспомогательных функций.
Задание
ЛегкоДавайте создадим систему безопасного обмена сообщениями, в которой классу MessageReader требуется специальный доступ для чтения зашифрованных сообщений, хранящихся в классе SecureMessage. Это идеальный сценарий для использования дружественных функций и дружественных классов.
Вы создадите два файла для организации вашего кода:
SecureMessage.h: Определите классSecureMessage, который хранит приватные данные сообщения и предоставляет выборочный доступ доверенным сущностям. Ваш класс должен иметь:- Приватные члены:
content(string) иsecretKey(int) - Конструктор, который принимает содержимое и секретный ключ
- Объявите
MessageReaderкак дружественный класс (friend class), чтобы он мог обращаться к приватным членам - Объявите дружественную функцию
decryptMessage(const SecureMessage& msg, int key), которая возвращает содержимое только в том случае, если предоставленный ключ совпадает с секретным ключом, в противном случае возвращает"Access Denied"
Затем определите класс
MessageReaderв том же заголовочном файле:- Метод
readContent(const SecureMessage& msg), который напрямую обращается к приватному членуcontentсообщения и возвращает его - Метод
verifyKey(const SecureMessage& msg, int key), который проверяет, совпадает ли предоставленный ключ сsecretKeyсообщения, и возвращает"Valid"или"Invalid"
Наконец, определите дружественную функцию
decryptMessageпосле классов.- Приватные члены:
main.cpp: Продемонстрируйте работу дружественных функций и дружественных классов. Считайте содержимое сообщения, секретный ключ и тестовый ключ из входных данных (каждый на отдельной строке), затем:- Создайте объект
SecureMessageс содержимым и секретным ключом - Создайте объект
MessageReader - Выведите
"Reader access: <content>", используя метод ридераreadContent() - Выведите
"Key verification: <result>", используяverifyKey()с тестовым ключом - Выведите
"Decrypt with test key: <result>", используя дружественную функцию с тестовым ключом - Выведите
"Decrypt with correct key: <result>", используя дружественную функцию с оригинальным секретным ключом
- Создайте объект
Помните, что дружественные функции определяются вне класса без оператора разрешения области видимости — это обычные функции со специальными привилегиями доступа. Класс MessageReader, будучи дружественным, может напрямую обращаться к приватным членам SecureMessage во всех своих методах.
Преобразуйте входные данные ключей из строк в целые числа с помощью std::stoi(). Подключите ваш заголовочный файл в main.cpp с помощью #include "SecureMessage.h".
Шпаргалка
Ключевое слово friend предоставляет выборочный доступ к закрытым (private) членам, сохраняя при этом инкапсуляцию.
Дружественная функция (Friend Function): Функция, объявленная внутри класса, которая может обращаться к его закрытым (private) и защищенным (protected) членам, но не является членом этого класса:
class Box {
double width;
public:
Box(double w) : width(w) {}
// Объявление дружественной функции
friend void printWidth(const Box& b);
};
// Определение вне класса без оператора разрешения области видимости
void printWidth(const Box& b) {
std::cout << b.width; // Может обращаться к закрытому члену
}Дружественный класс (Friend Class): Весь класс может быть объявлен дружественным, что дает всем его методам доступ к закрытым членам другого класса:
class Engine {
int horsepower;
friend class Mechanic; // Все методы Mechanic могут обращаться к закрытым членам
public:
Engine(int hp) : horsepower(hp) {}
};
class Mechanic {
public:
void diagnose(const Engine& e) {
std::cout << e.horsepower; // Разрешено
}
};Ключевые моменты:
- Дружественность не является взаимной: если класс A объявляет класс B своим другом, то B может обращаться к закрытым членам A, но не наоборот
- Дружественные функции определяются вне класса без использования оператора разрешения области видимости
- Используйте дружественные отношения умеренно, так как они создают сильную связанность (tight coupling) между классами
- Наиболее полезны для перегрузки операторов и тесно связанных вспомогательных функций
Попробуйте сами
#include <iostream>
#include <string>
#include "SecureMessage.h"
using namespace std;
int main() {
// Чтение входных данных
string messageContent;
string secretKeyStr;
string testKeyStr;
getline(cin, messageContent);
getline(cin, secretKeyStr);
getline(cin, testKeyStr);
int secretKey = stoi(secretKeyStr);
int testKey = stoi(testKeyStr);
// TODO: Создайте SecureMessage с содержимым и secret key
// TODO: Создайте объект MessageReader
// TODO: Выведите "Reader access: <content>", используя readContent()
// TODO: Выведите "Key verification: <result>", используя verifyKey() с test key
// TODO: Выведите "Decrypt with test key: <result>", используя decryptMessage с test key
// TODO: Выведите "Decrypt with correct key: <result>", используя decryptMessage с secret key
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер