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



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




  • Является ли идиома pImpl действительно используется на практике?

    11 ответов



Backgrounder:



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



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



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



чтобы проиллюстрировать, этот код ставит Purr() реализация в классе impl и обертывает его как что ж.



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



// 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");
}
702   11  

11 ответов:

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

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

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

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

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

мы сделали это с переписыванием продукта IONAs Orbix 3.3 В 2000.

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

этот метод используется в методологии под названием оформление по договору.

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

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

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

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

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

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

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

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

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

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

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

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

фу.h:

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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Comments

    Ничего не найдено.