Идиома pimpl (pointer to implementation — указатель на реализацию) полезна в тех случаях, когда нам нужно что-то скрыть. Она обеспечивает еще более глубокий вид инкапсуляции, которая маскирует не просто реализацию, а также все ее зависимости.
Например, мы создаем библиотеку, которая сама зависит от сторонних библиотек. При этом мы собираемся передать нашу библиотеку кому-то другому без исходников. Чтобы не тянуть зависимости в виде заголовочных файлов (как минимум) и не раскрыть слишком много деталей реализации в интерфейсе класса (как максимум), мы можем воспользоваться идиомой pimpl.
Создаем класс, который нужно замаскировать
Не будем усложнять пример. Рассмотрим лишь общую механику работы pimpl в искусственной ситуации. Идиома является хорошо масштабируемой и без труда распространяется на значительно более сложные проекты.
Заголовочный файл myclass.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#ifndef MYCLASS_H #define MYCLASS_H class MyClassImpl; class MyClass { public: MyClass(); ~MyClass(); int callMe( int arg ); private: MyClassImpl* pimpl; }; #endif // MYCLASS_H |
Обратите внимание, что мы ссылаемся на класс реализации не через #include, а с помощью объявления class MyClassImpl. В этом случае мы можем создавать указатель на этот класс не имея заголовочного файла (который мы и не собираемся распространять).
Переходим к реализации ( myclass.cpp):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include "myclass.h" #include "myclassimpl.h" #include <QDebug> MyClass::MyClass() : pimpl( new MyClassImpl ) { qDebug() << "MyClass ctor"; } MyClass::~MyClass() { qDebug() << "MyClass dtor"; delete pimpl; } int MyClass::callMe( int arg ) { return pimpl->callMe( arg ); } |
Здесь нам уже потребовалось подключить заголовочный файл для MyClassImpl. Но эта часть относится к реализации, поэтому все в порядке. Вызов каждой открытой функции MyClass делегируется соответствующей функции-члену реализации MyClassImpl:
1 2 3 |
int MyClass::callMe( int arg ) { return pimpl->callMe( arg ); } |
Класс-реализация
Здесь все еще проще. Заголовочный файл myclassimpl.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#ifndef MYCLASSIMPL_H #define MYCLASSIMPL_H #include <myclass.h> #include <string> class MyClassImpl { public: MyClassImpl(); ~MyClassImpl(); int callMe( int arg ); private: // Просто так. Чтобы было int a; std::string s; }; #endif // MYCLASSIMPL_H |
Реализация в myclassimpl.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include "myclassimpl.h" #include <QDebug> MyClassImpl::MyClassImpl() { qDebug() << "MyClassImpl ctor"; } MyClassImpl::~MyClassImpl() { qDebug() << "MyClassImpl dtor"; } int MyClassImpl::callMe( int arg ) { return arg; } |
Все вместе
Файл main.cpp:
1 2 3 4 5 6 7 8 9 10 |
#include <QDebug> #include "myclass.h" int main() { MyClass a; qDebug() << a.callMe( 5 ); return 0; } |
Основное здесь то, что компилятор не требует наличия файла myclassimpl.h в контексте main.cpp. А этого мы и хотели.