Dlaczego warto używać idiomu "PIMPL"? [duplikat]

To pytanie ma już odpowiedź tutaj:

  • Czy idiom pImpl jest naprawdę używany w praktyce? 11 odpowiedzi

Backgrounder:

The Pimpl Idiom (Wskaźnik do implementacji) jest techniką ukrywania implementacji, w której publiczna Klasa otacza strukturę lub klasę, której nie można zobaczyć poza biblioteką, której publiczna klasa jest częścią z.

To ukrywa wewnętrzne szczegóły implementacji i dane od użytkownika biblioteki.

Implementując ten idiom, dlaczego umieszczasz publiczne metody na klasie pimpl, a nie na klasie publicznej, ponieważ implementacje metod klasy publicznej byłyby skompilowane do biblioteki, a użytkownik ma tylko plik nagłówkowy?

Aby zilustrować, kod ten umieszcza implementację Purr() na klasie impl i owija ją również.

Dlaczego nie wdrożyć Purr bezpośrednio na zajęciach publicznych?

// 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");
}
Author: Peter G., 2008-09-13

11 answers

  • ponieważ chcesz Purr(), aby móc korzystać z prywatnych członków CatImpl. Cat::Purr() nie uzyskałby takiego dostępu bez deklaracji friend.
  • ponieważ wtedy nie mieszasz obowiązków: jedna klasa wdraża, jedna klasa przechodzi do przodu.
 37
Author: Xavier Nodet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-12 09:49:04

Myślę, że większość ludzi odnosi się do tego idiomu ciała uchwytu. Zobacz książkę Jamesa Copliena Advanced C++ Programming Styles and Idioms (Amazon link). Jest również znany jako Kot z Cheshire ze względu na postać Lewisa Carolla, która zanika, dopóki nie zostanie tylko uśmiech.

Przykładowy kod powinien być rozłożony na dwa zestawy plików źródłowych. Potem tylko Cat.h to plik dostarczany wraz z produktem.

CatImpl.h obejmuje Cat.cpp i CatImpl.cpp zawiera implementację CatImpl:: Purr (). To nie będzie widoczne dla publiczności przy użyciu produktu.

Zasadniczo chodzi o to, aby ukryć jak najwięcej implementacji dla wścibskich oczu. Jest to najbardziej przydatne, gdy masz produkt komercyjny, który jest dostarczany jako seria bibliotek, do których dostęp jest możliwy za pośrednictwem interfejsu API, z którym kod klienta jest kompilowany i linkowany.

Zrobiliśmy to z przepisaniem produktu Ionas Orbix 3.3 w 2000 roku.

Jak wspomniano przez inni, używając jego techniki, całkowicie oddzielają realizację od interfejsu obiektu. Wtedy nie będziesz musiał przekompilować wszystkiego, co używa Cat, jeśli chcesz zmienić implementację Purr ().

Technika ta jest stosowana w metodologii zwanej design by contract.

 52
Author: Rob Wells,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-11 14:24:19

Co jest warte, to oddziela implementację od interfejsu. Zwykle nie jest to bardzo ważne w małych projektach. Ale w dużych projektach i bibliotekach można go wykorzystać do znacznego skrócenia czasu budowy.

Rozważ, że implementacja Cat może zawierać wiele nagłówków, może obejmować meta-programowanie szablonów, które wymaga czasu na samodzielną kompilację. Dlaczego użytkownik, który chce tylko korzystać z Cat musi to wszystko uwzględnić? Stąd wszystkie niezbędne pliki są ukryte za pomocą idiomu pimpl (stąd deklaracja forward CatImpl), a użycie interfejsu nie zmusza użytkownika do ich włączenia.

Rozwijam bibliotekę do nieliniowej optymalizacji (Czytaj "lots of nasty math"), która jest zaimplementowana w szablonach, więc większość kodu znajduje się w nagłówkach. Kompilacja zajmuje około pięciu minut (na przyzwoitym wielordzeniowym procesorze), a samo parsowanie nagłówków w pustym .cpp zajmuje około minuty. Więc każdy kto korzysta z biblioteki musi poczekać kilka minut za każdym razem, gdy kompilują swój kod, co sprawia, że rozwój jest dość żmudny {10]}. Jednak ukrywając implementację i nagłówki, jeden zawiera tylko prosty plik interfejsu, który kompiluje się natychmiast.

Nie musi to mieć nic wspólnego z ochroną implementacji przed kopiowaniem przez inne firmy - co i tak by się nie stało, chyba że wewnętrzne działanie Twojego algorytmu można odgadnąć z definicji zmiennych członkowskich (jeśli tak, to prawdopodobnie nie jest to zbyt skomplikowane i nie warto chronić w pierwszej kolejności).

 17
Author: the swine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-23 11:18:03

Jeśli twoja klasa używa idiomu pimpl, możesz uniknąć zmiany pliku nagłówkowego w publicznej klasie.

To pozwala na dodawanie / usuwanie metod do klasy pimpl, bez modyfikowania pliku nagłówkowego klasy zewnętrznej. Możesz również dodać /usunąć # includes do pimpl.

Kiedy zmienisz plik nagłówka klasy zewnętrznej, musisz przekompilować wszystko, co zawiera #(i jeśli któreś z nich są plikami nagłówkowymi, musisz przekompilować wszystko, co zawiera#, itd.)

 14
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-06 11:26:30

Zazwyczaj jedynym odniesieniem do klasy Pimpl w nagłówku dla klasy Owner (w tym przypadku Cat) byłaby deklaracja forward, jak to zrobiłeś tutaj, ponieważ może to znacznie zmniejszyć zależności.

Na przykład, jeśli twoja klasa Pimpl ma ComplicatedClass jako członek (a nie tylko wskaźnik lub odniesienie do niego), musisz mieć ComplicatedClass w pełni zdefiniowany przed jej użyciem. W praktyce oznacza to włączenie "Complicatedlass.h" (które także pośrednio obejmie wszystko od czego zależy). Może to prowadzić do pojedynczego wypełnienia nagłówka wciągającego wiele rzeczy, co jest złe dla zarządzania zależnościami (i czasem kompilacji).

Kiedy używasz pimpl idion, musisz tylko # dołączyć rzeczy używane w publicznym interfejsie typu właściciela(który byłby tutaj Cat). Co sprawia, że rzeczy lepiej dla ludzi korzystających z biblioteki, i oznacza, że nie musisz martwić się o ludzi w zależności od jakiejś wewnętrznej części biblioteki - albo przez błąd, lub dlatego, że chcą zrobić coś, na co nie pozwalasz, więc # definiują prywatne publiczne przed włączeniem plików.

Jeśli jest to prosta klasa, zwykle nie ma powodu, aby używać Pimpl, ale w czasach, gdy typy są dość duże, może to być bardzo pomocne (szczególnie w unikaniu długich czasów budowania)

 6
Author: Wilka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-13 14:53:59

Nie użyłbym tego. Mam lepszą alternatywę:

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;
}

Czy ten wzór ma jakąś nazwę?

Jako programista Python i Java, podoba mi się to o wiele bardziej niż idiom pImpl.

 4
Author: Esben Nielsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-21 13:30:54

Umieszczenie wywołania do impl - > Purr wewnątrz pliku cpp oznacza, że w przyszłości możesz zrobić coś zupełnie innego bez konieczności zmiany pliku nagłówkowego. Może w przyszłym roku odkryją metodę pomocniczą, którą mogliby nazwać zamiast niej i mogą zmienić kod, aby wywołać ją bezpośrednio i w ogóle nie używać impl - >Purr. (Tak, mogą osiągnąć to samo, aktualizując rzeczywistą metodę impl:: Purr, ale w takim przypadku utknąłeś z dodatkowym wywołaniem funkcji, które osiąga nic poza wywołaniem następnej funkcji)

Oznacza to również, że nagłówek ma tylko definicje i nie ma żadnej implementacji, która sprawia, że oddzielenie jest czystsze, co jest sednem idiomu.

 2
Author: Phil Wright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-13 14:52:32

Właśnie wdrożyłem moje pierwsze zajęcia pimpl w ciągu ostatnich kilku dni. Użyłem go do wyeliminowania problemów, które miałem, w tym winsock2.h W Borland Builder. Wydawało się, że psuje to wyrównanie struktury, a ponieważ miałem socket w klasie private data, te problemy rozprzestrzeniały się na dowolny plik cpp, który zawierał nagłówek.

Używając pimpl, winsock2.h był zawarty tylko w jednym pliku cpp, gdzie mogłem umieścić pokrywę problemu i nie martwić się, że wróci do gryźć ja.

Aby odpowiedzieć na pierwotne pytanie, zaletą, jaką znalazłem w przekierowaniu wywołań do klasy pimpl było to, że Klasa pimpl jest taka sama jak twoja oryginalna Klasa, zanim ją przepiszesz, plus Twoje implementacje nie są rozłożone na 2 klasy w jakiś dziwny sposób. Dużo jaśniej jest zaimplementować publikę, aby po prostu przejść do klasy pimpl.

Jak powiedział Pan Nodet, jedna klasa, jedna odpowiedzialność.

 1
Author: markh44,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-24 16:34:13

Używamy idiomu PIMPL w celu emulowania programowania aspect oriented, gdzie aspekty pre, post i error są wywoływane przed i po wykonaniu funkcji member.

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

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

Używamy również wskaźnika do klasy bazowej, aby współdzielić różne aspekty między wieloma klasami.

Wadą tego podejścia jest to, że użytkownik biblioteki musi brać pod uwagę wszystkie aspekty, które będą wykonywane, ale widzi tylko swoją klasę. Wymaga przeglądania dokumentacji dla wszelkich skutki uboczne.
 1
Author: nurettin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-11 18:20:59

Nie wiem, czy warto o tym wspomnieć, ale...

Czy możliwe byłoby posiadanie implementacji we własnej przestrzeni nazw i posiadanie publicznej przestrzeni nazw wrappera / biblioteki dla kodu widzianego przez użytkownika:

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

W ten sposób cały kod biblioteki może korzystać z przestrzeni nazw cat, a gdy zachodzi potrzeba udostępnienia klasy użytkownikowi, w przestrzeni nazw catlib może zostać utworzony wrapper.

 0
Author: JeffV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-13 15:31:47

Uważam, że to mówi, że pomimo tego, jak dobrze znany jest idiom pimpl, nie widzę go zbyt często w prawdziwym życiu (np. w projektach open source).

Często zastanawiam się, czy "korzyści" są przesadne; tak, możesz sprawić, że niektóre szczegóły Twojej implementacji będą jeszcze bardziej ukryte, i tak, możesz zmienić swoją implementację bez zmiany nagłówka, ale nie jest oczywiste, że są to duże zalety w rzeczywistości.

To znaczy, nie jest jasne, że jest jakaś potrzeba dla implementacja ma być , która jest dobrze ukryta, i być może jest to dość rzadkie, że ludzie naprawdę zmieniają tylko implementację; jak tylko trzeba dodać nowe metody, powiedzmy, że i tak trzeba zmienić nagłówek.

 0
Author: DrPizza,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-09-15 09:54:01