Menu
Coddy logo textTech

Идиома Pimpl

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 78 из 104.

Идиома Pimpl (Pointer to Implementation) — это техника, которая скрывает детали реализации class, перемещая их в отдельный предварительно объявленный class. Это уменьшает зависимости при компиляции и обеспечивает действительно полное сокрытие приватных членов от файла header.

Основная идея проста: вместо объявления закрытых членов непосредственно в вашем классе вы объявляете указатель на класс реализации, который определён только в исходном файле:

// Widget.h
#include <memory>

class Widget {
public:
    Widget();
    ~Widget();
    void doSomething();
    
private:
    class Impl;  // Предварительное объявление
    std::unique_ptr<Impl> pImpl;
};
// Widget.cpp
#include "Widget.h"
#include <iostream>

class Widget::Impl {
public:
    int data = 42;
    void process() { std::cout << "Processing: " << data << "\n"; }
};

Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;

void Widget::doSomething() { pImpl->process(); }

Ключевые преимущества — это барьер компиляции и бинарная совместимость. Когда вы изменяете класс Impl, перекомпилировать нужно только исходный файл, а не каждый файл, который включает header. Это значительно ускоряет время сборки в крупных проектах.

Обратите внимание, что destructor должен быть defined в исходном file (даже если он defaulted), потому что unique_ptr нужен complete тип Impl, чтобы удалить его. Это распространённая ошибка при первом использовании Pimpl.

challenge icon

Задание

Легко

Давайте создадим обработчик безопасных сообщений с использованием идиомы Pimpl, чтобы скрыть детали реализации encryption от header-файла. Это демонстрирует, как Pimpl создаёт compilation firewall: любой, кто подключает ваш header, не увидит внутреннее устройство обработки сообщений.

Вы организуете код в трёх файлах:

  • SecureMessage.h: Define публичный интерфейс для вашего SecureMessage class.

    Ваш class должен содержать предварительно объявленный Impl class и std::unique_ptr на него. Публичный интерфейс должен включать:

    • constructor, принимающий const std::string& для исходного сообщения
    • destructor (должен быть declared здесь, defined в .cpp-файле)
    • метод setKey(int key) для установки encryption key
    • метод getEncrypted(), возвращающий encrypted сообщение как std::string
    • метод getOriginal(), возвращающий исходное сообщение

    В header должен отображаться только публичный интерфейс: здесь не должны быть видны детали реализации того, как работает encryption.

  • SecureMessage.cpp: Define вложенный Impl class и Implement все методы.

    Ваш Impl class должен хранить исходное сообщение, encryption key (default to 0) и обрабатывать фактическую логику encryption. Используйте простой Caesar cipher для encryption: сдвигайте каждый символ на значение key. Например, при key 3 символ 'a' становится 'd', а 'z' переходит по кругу к 'c'.

    Encryption должен влиять только на строчные буквы (a-z), оставляя все остальные символы без изменений. Не забудьте Define destructor здесь (даже если он defaulted), поскольку unique_ptr нужен complete тип Impl.

  • main.cpp: Считайте два входных значения:
    1. Строку сообщения (может содержать пробелы)
    2. Encryption key (целое число)

    Create объект SecureMessage, установите key и выведите результаты:

    1. Выведите Original: , за которым следует исходное сообщение
    2. Выведите Encrypted: , за которым следует encrypted сообщение

Например, при входных значениях hello world и 3:

Original: hello world
Encrypted: khoor zruog

При входных значениях xyz abc и 5:

Original: xyz abc
Encrypted: cde fgh

Обратите внимание, что header-файл ничего не раскрывает о реализации Caesar cipher: в этом и заключается сила Pimpl. Если позднее вы измените алгоритм encryption, перекомпилировать потребуется только SecureMessage.cpp, а не любой файл, включающий header.

Попробуйте сами

#include <iostream>
#include <string>
#include "SecureMessage.h"

int main() {
    // Прочитать сообщение (может содержать пробелы)
    std::string message;
    std::getline(std::cin, message);
    
    // Прочитать ключ шифрования
    int key;
    std::cin >> key;
    
    // TODO: Создать объект SecureMessage с сообщением
    
    // TODO: Установить ключ шифрования
    
    // TODO: Вывести "Original: " за которым следует исходное сообщение
    
    // TODO: Вывести "Encrypted: " за которым следует зашифрованное сообщение
    
    return 0;
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор C++