Почему следует использовать идиому «PIMPL»?[дубликат]

StackOverflow https://stackoverflow.com/questions/60570

Вопрос

На этот вопрос уже есть ответ здесь:

Справочник:

А Идиома PIMPL (Указатель на реализацию) — это метод сокрытия реализации, при котором общедоступный класс обертывает структуру или класс, который нельзя увидеть за пределами библиотеки, частью которой является общедоступный класс.

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

При реализации этой идиомы зачем вам размещать общедоступные методы в классе 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");
}
Это было полезно?

Решение

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

фу.ч:

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

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

фу.cpp:

namespace {
    class FooImpl: virtual public Foo {

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

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

Есть ли у этого шаблона название?

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

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

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

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

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

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

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

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

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

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

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

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

Не знаю, стоит ли упоминать об этой разнице, но...

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top