Почему должен использоваться идиома "PIMPL"?

Справка:

ИДОМ PIMPL (Указатель на IMPLementation) - это метод скрытия реализации, в котором открытый класс обертывает структуру или класс, которые невозможно увидеть вне библиотеки публичный класс является частью.

Это скрывает внутренние детали реализации и данные от пользователя библиотеки.

При реализации этой идиомы, почему вы размещаете общедоступные методы в классе pimpl, а не в публичном классе, поскольку реализации методов общедоступных классов будут скомпилированы в библиотеку, а пользователь имеет только заголовочный файл?

Чтобы проиллюстрировать этот код, он помещает реализацию Purr() в класс impl и обертывает его.

Почему бы не реализовать Purr непосредственно в открытом классе?

// header file:
class Cat {
    private:
        class CatImpl;  // Not defined here
        CatImpl *cat_;  // Handle

    public:
        Cat();            // Constructor
        ~Cat();           // Destructor
        // Other operations...
        Purr();
};


// CPP file:
#include "cat.h"

class Cat::CatImpl {
    Purr();
...     // The actual implementation can be anything
};

Cat::Cat() {
    cat_ = new CatImpl;
}

Cat::~Cat() {
    delete cat_;
}

Cat::Purr(){ cat_->Purr(); }
CatImpl::Purr(){
   printf("purrrrrr");
}

Ответ 1

  • Потому что вы хотите, чтобы Purr() мог использовать частные члены CatImpl. Cat::Purr() не будет разрешен такой доступ без объявления friend.
  • Потому что тогда вы не смешиваете обязанности: один класс реализует один класс вперед.

Ответ 2

Я думаю, что большинство людей ссылаются на это как на иероглиф. См. Книгу Джеймса Коплиена "Расширенные стили и идиомы программирования на C++" (ссылка Amazon). Он также известен как Чеширский кот из-за характера Льюиса Кэролла, который исчезает, пока не останется только улыбка.

Пример кода должен быть распределен между двумя наборами исходных файлов. Тогда только Cat.h - это файл, который поставляется вместе с продуктом.

CatImpl.h включен в Cat.cpp, а CatImpl.cpp содержит реализацию для CatImpl:: Purr(). Это не будет видно для общественности, используя ваш продукт.

В принципе, идея состоит в том, чтобы скрыть как можно больше возможностей для поиска. Это наиболее полезно, если у вас есть коммерческий продукт, который поставляется как серия библиотек, к которым осуществляется доступ через API, с которым компилируется код клиента и связан с ним.

Мы сделали это с переделкой продукта IONAs Orbix 3.3 в 2000 году.

Как упоминалось другими, использование его метода полностью отделяет реализацию от интерфейса объекта. Тогда вам не придется перекомпилировать все, что использует Cat, если вы просто хотите изменить реализацию Purr().

Этот метод используется в методологии дизайн по контракту.

Ответ 3

Для того, что стоит, он отделяет реализацию от интерфейса. Обычно это не очень важно для небольших проектов. Но в больших проектах и ​​библиотеках его можно использовать для значительного сокращения времени сборки.

Учтите, что реализация Cat может включать в себя множество заголовков, может включать метапрограммирование шаблона, которое требует времени для компиляции самостоятельно. Почему пользователь, который просто хочет использовать Cat, должен включить все это? Следовательно, все необходимые файлы спрятаны с помощью идиомы pimpl (отсюда и объявление вперед CatImpl), а использование интерфейса не заставляет пользователя их включать.

Я разрабатываю библиотеку для нелинейной оптимизации (читаем "много неприятной математики" ), которая реализована в шаблонах, поэтому большая часть кода находится в заголовках. Требуется около пяти минут для компиляции (на приличном многоядерном процессоре), и просто разбор заголовков в противном случае пустой .cpp занимает около минуты. Поэтому каждый, кто пользуется библиотекой, должен ждать пару минут каждый раз, когда компилирует свой код, что делает разработку довольно утомительной. Однако, скрывая реализацию и заголовки, вы просто включаете простой файл интерфейса, который компилируется мгновенно.

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

Ответ 4

Если ваш класс использует идиому pimpl, вы можете избежать изменения файла заголовка в открытом классе.

Это позволяет добавлять/удалять методы в класс pimpl, не изменяя внешний файл заголовка внешнего класса. Вы также можете добавить/удалить #includes в pimpl.

При изменении внешнего файла заголовка внешнего класса вам необходимо перекомпилировать все, что # включает его (и если какой-либо из них является файлами заголовков, вам необходимо перекомпилировать все, что # включает их и т.д.)

Ответ 5

Как правило, единственная ссылка на класс Pimpl в заголовке для класса Owner (Cat в этом случае) будет объявлением вперед, как вы это сделали, потому что это может значительно уменьшить зависимости.

Например, если ваш класс Pimpl имеет ComplicatedClass в качестве члена (а не только указатель или ссылку на него), вам нужно будет полностью определить ComplicatedClass до его использования. На практике это означает включение "ComplicatedClass.h" (которое также косвенно включает в себя все, от чего зависит ComplicatedClass). Это может привести к тому, что один заголовок заполняет много и много вещей, что плохо для управления вашими зависимостями (и временем компиляции).

Когда вы используете idim pimpl, вам нужно всего лишь # включить материал, используемый в открытом интерфейсе вашего типа Owner (который будет здесь Cat). Что делает вещи лучше для людей, использующих вашу библиотеку, и означает, что вам не нужно беспокоиться о людях в зависимости от какой-либо внутренней части вашей библиотеки - либо по ошибке, либо потому, что они хотят сделать что-то, чего вы не разрешаете, поэтому они #define перед публикацией ваших файлов.

Если это простой класс, обычно нет причин использовать Pimpl, но в те времена, когда типы довольно велики, это может быть большой помощью (особенно во избежание длительного времени сборки)

Ответ 6

Хорошо, я бы не использовал его. У меня есть лучшая альтернатива:

foo.h:

class Foo {
public:
    virtual ~Foo() { }
    virtual void someMethod() = 0;

    // This "replaces" the constructor
    static Foo *create();
}

foo.cpp:

namespace {
    class FooImpl: virtual public Foo {

    public:
        void someMethod() { 
            //....
        }     
    };
}

Foo *Foo::create() {
    return new FooImpl;
}

У этого шаблона есть имя?

Как и программист Python и Java, мне это нравится намного больше, чем идиома pImpl.

Ответ 7

Размещение вызова impl- > Purr внутри файла cpp означает, что в будущем вы можете сделать что-то совершенно другое, не изменяя файл заголовка. Возможно, в следующем году они обнаружат вспомогательный метод, который они могли бы назвать вместо этого, и поэтому они могут изменить код для прямого вызова, а не использовать impl- > Purr вообще. (Да, они могли бы добиться того же самого, обновив метод impl:: Purr, но в этом случае вы застряли с дополнительным вызовом функции, который ничего не добивается, но вызывает очередную функцию по очереди)

Это также означает, что заголовок имеет только определения и не имеет никакой реализации, что делает более чистое разделение, что является всей точкой идиомы.

Ответ 8

Я только что реализовал свой первый класс pimpl за последние пару дней. Я использовал его для устранения проблем, с которыми я столкнулся, включая winsock2.h в Borland Builder. Казалось, что он закручивает выравнивание структуры, и поскольку у меня были сокетные вещи в частных данных класса, эти проблемы распространялись на любой файл cpp, который включал заголовок.

Используя pimpl, winsock2.h был включен только в один файл cpp, где я мог бы установить крышку на проблему и не волноваться, что она вернется, чтобы укусить меня.

Чтобы ответить на исходный вопрос, преимущество, которое я обнаружил при пересылке вызовов классу pimpl, заключалось в том, что класс pimpl такой же, какой был бы у вашего первоначального класса до того, как вы его pimpl'd, плюс ваши реализации не будут распространяются по 2 класса некоторым странным образом. Гораздо яснее реализовать публику, чтобы просто перейти к классу pimpl.

Как сказал г-н Нодет, один класс, одна ответственность.

Ответ 9

Мы используем идиом PIMPL, чтобы эмулировать аспектно-ориентированное программирование, где до, после и после выполнения функции-члена вызывается предварительные, пост-и ошибки.

struct Omg{
   void purr(){ cout<< "purr\n"; }
};

struct Lol{
  Omg* omg;
  /*...*/
  void purr(){ try{ pre(); omg-> purr(); post(); }catch(...){ error(); } }
};

Мы также используем указатель на базовый класс для обмена различными аспектами между многими классами.

Недостатком этого подхода является то, что пользователь библиотеки должен учитывать все аспекты, которые будут выполняться, но видит только его класс. Он требует просмотра документации по любым побочным эффектам.

Ответ 10

Я не знаю, стоит ли упоминать это различие, но...

Можно ли реализовать реализацию в своем собственном пространстве имен и иметь общедоступное пространство имен оболочки/библиотеки для кода, который видит пользователь:

catlib::Cat::Purr(){ cat_->Purr(); }
cat::Cat::Purr(){
   printf("purrrrrr");
}

Таким образом, весь код библиотеки может использовать пространство имен кошек, и по мере того, как необходимость выставлять класс пользователю, оболочка может быть создана в пространстве имен catlib.

Ответ 11

Я нахожу, что это говорит о том, что, несмотря на то, насколько хорошо известна идиома pimpl, я не вижу ее очень часто в реальной жизни (например, в проектах с открытым исходным кодом).

Я часто задаюсь вопросом, раздуты ли "преимущества"; да, вы можете сделать некоторые детали своей реализации еще более скрытыми, и да, вы можете изменить свою реализацию, не изменяя заголовок, но не очевидно, что это большие преимущества в реальности.

То есть, неясно, есть ли какая-то потребность в вашей реализации, которая хорошо скрыта, и, возможно, очень редко, что люди действительно меняют только реализацию; как только вам нужно добавить новые методы, скажем, вам все равно нужно изменить заголовок.