Идиома 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.
Задание
ЛегкоДавайте создадим обработчик безопасных сообщений с использованием идиомы Pimpl, чтобы скрыть детали реализации encryption от header-файла. Это демонстрирует, как Pimpl создаёт compilation firewall: любой, кто подключает ваш header, не увидит внутреннее устройство обработки сообщений.
Вы организуете код в трёх файлах:
SecureMessage.h: Define публичный интерфейс для вашегоSecureMessageclass.Ваш class должен содержать предварительно объявленный
Implclass иstd::unique_ptrна него. Публичный интерфейс должен включать:- constructor, принимающий
const std::string&для исходного сообщения - destructor (должен быть declared здесь, defined в .cpp-файле)
- метод
setKey(int key)для установки encryption key - метод
getEncrypted(), возвращающий encrypted сообщение какstd::string - метод
getOriginal(), возвращающий исходное сообщение
В header должен отображаться только публичный интерфейс: здесь не должны быть видны детали реализации того, как работает encryption.
- constructor, принимающий
SecureMessage.cpp: Define вложенныйImplclass и Implement все методы.Ваш
Implclass должен хранить исходное сообщение, 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: Считайте два входных значения:- Строку сообщения (может содержать пробелы)
- Encryption key (целое число)
Create объект
SecureMessage, установите key и выведите результаты:- Выведите
Original:, за которым следует исходное сообщение - Выведите
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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++