IT Notes

Идиома pimpl в C++: Указатель на реализацию

Идиома pimpl (pointer to implementation - указатель на реализацию) полезна в тех случаях, когда нам нужно что-то скрыть. Она обеспечивает еще более глубокий вид инкапсуляции, которая маскирует не просто реализацию, а также все ее зависимости.

Например, мы создаем библиотеку, которая сама зависит от сторонних библиотек. При этом мы собираемся передать нашу библиотеку кому-то другому без исходников. Чтобы не тянуть зависимости в виде заголовочных файлов (как минимум) и не раскрыть слишком много деталей реализации в интерфейсе класса (как максимум), мы можем воспользоваться идиомой pimpl.

Создаем класс, который нужно замаскировать

Не будем усложнять пример. Рассмотрим лишь общую механику работы pimpl в искусственной ситуации. Идиома является хорошо масштабируемой и без труда распространяется на значительно более сложные проекты.

Заголовочный файл myclass.h:

#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):

#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:

int MyClass::callMe( int arg ) {
    return pimpl->callMe( arg );
}

Класс-реализация

Здесь все еще проще. Заголовочный файл myclassimpl.h:

#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:

#include "myclassimpl.h"

#include <QDebug>

MyClassImpl::MyClassImpl() {
    qDebug() << "MyClassImpl ctor";
}

MyClassImpl::~MyClassImpl() {
    qDebug() << "MyClassImpl dtor";
}

int MyClassImpl::callMe( int arg ) {
    return arg;
}

Все вместе

Файл main.cpp:

#include <QDebug>

#include "myclass.h"

int main() {
    MyClass a;
    qDebug() << a.callMe( 5 );

    return 0;
}

Основное здесь то, что компилятор не требует наличия файла myclassimpl.h в контексте main.cpp. А этого мы и хотели.

Исходники

 Скачать пример использования идиомы pimpl в C++

Похожие публикации