Menu
Coddy logo textTech

Дружественные функции и классы

Часть раздела Объектно-ориентированное программирование путешествия по 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, но не наоборот. Используйте дружественные отношения умеренно, так как они создают сильную связанность между классами. Они наиболее полезны для перегрузки операторов и тесно связанных вспомогательных функций.

challenge icon

Задание

Легко

Давайте создадим систему безопасного обмена сообщениями, в которой классу 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;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование