Pimpl idiom vs pure virtual class interface

Zastanawiałem się, co sprawi, że programista wybierze albo idiom Pimpl, albo czystą klasę wirtualną i dziedziczenie.

Rozumiem, że idiom pimpl zawiera jedną wyraźną dodatkową indirection dla każdej publicznej metody i tworzenia obiektu.

Pure virtual class Z drugiej strony jest wyposażony w implicit indirection (vtable) dla implementacji dziedziczącej i rozumiem, że nie ma narzutu na tworzenie obiektów.
EDIT : ale potrzebujesz fabryki, jeśli utworzysz obiekt z zewnątrz

Co sprawia, że Klasa pure virtual jest mniej pożądana niż idiom pimpl?

Author: Arkaitz Jimenez, 2009-05-05

10 answers

Pisząc klasę C++ warto zastanowić się czy będzie to

  1. Typ Wartości

    Kopiuj według wartości, tożsamość nigdy nie jest ważna. Jest to właściwe, aby był to klucz w std:: map. Przykład: klasa "string", Klasa" date "lub klasa " complex number". "Kopiowanie" instancji takiej klasy ma sens.

  2. Typ podmiotu

    Tożsamość jest ważna. Zawsze przekazywane przez odniesienie, nigdy przez "wartość". Często nie sprawia sens "kopiowania" instancji klasy w ogóle. Gdy ma to sens, polimorficzna metoda" klonowania " jest zwykle bardziej odpowiednia. Przykłady: Klasa gniazd, Klasa bazy danych, Klasa "policy", cokolwiek, co byłoby "zamknięciem" w języku funkcjonalnym.

Zarówno pimpl jak i Pure abstract base class są technikami redukującymi zależności w czasie kompilacji.

Jednak zawsze używam pImpl do implementacji typów wartości (typ 1) i tylko czasami, gdy naprawdę chcę zminimalizować sprzężenia i zależności w czasie kompilacji. Często nie warto się tym przejmować. Jak słusznie zauważyłeś, jest więcej narzutu składniowego, ponieważ musisz napisać metody przekazywania dla wszystkich metod publicznych. Dla klas typu 2 Zawsze używam czystej abstrakcyjnej klasy bazowej z powiązanymi metodami fabrycznymi.

 55
Author: Paul Hollingsworth,
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-05-06 09:05:04

Pointer to implementation zwykle polega na ukrywaniu szczegółów implementacji strukturalnej. Interfaces dotyczą tworzenia instancji różnych implementacji. Naprawdę służą dwóm różnym celom.

 31
Author: D.Shawley,
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-05-05 14:09:00

Idiom pimpl pomaga zredukować zależności i czasy budowania, szczególnie w dużych aplikacjach, i minimalizuje ekspozycję nagłówka szczegółów implementacji twojej klasy na jedną jednostkę kompilacji. Użytkownicy Twojej klasy nie powinni nawet być świadomi istnienia pryszcza (z wyjątkiem tajemniczego wskaźnika, do którego nie są wtajemniczeni!).

Klasy abstrakcyjne (pure virtuals) to coś, o czym Twoi klienci muszą być świadomi: jeśli spróbujesz ich użyć, aby zmniejszyć sprzężenie i okrężność referencje, musisz dodać jakiś sposób pozwalający im tworzyć twoje obiekty (np. poprzez metody fabryczne lub klasy, wstrzykiwanie zależności lub inne mechanizmy).

 26
Author: Pontus Gagge,
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-05-05 14:21:11

Szukałem odpowiedzi na to samo pytanie. Po przeczytaniu kilku artykułów i trochę praktyki wolę używać "Pure virtual class interfaces" .

  1. są bardziej proste (jest to subiektywna opinia). Idiom Pimpl sprawia, że czuję, że piszę kod "dla kompilatora", a nie dla" następnego programisty", który odczyta mój kod.
  2. niektóre frameworki testowe mają bezpośrednie wsparcie dla wyśmiewania czystych klas wirtualnych
  3. to prawda, że ty potrzeba fabryka dostępna z zewnątrz. Ale jeśli chcesz wykorzystać polimorfizm: to również "za", a nie "przekręt". ...a prosta metoda fabryczna nie boli tak bardzo

Jedyną wadą (próbuję to zbadać) jest to, że pimpl może być szybszy

  1. gdy wywołania proxy są inlinowane, podczas dziedziczenia muszą mieć dodatkowy dostęp do obiektu VTABLE w czasie wykonywania
  2. the memory footprint the pimpl public-proxy-class jest mniejszy (możesz łatwo optymalizować dla szybszych swapów i innych podobnych optymalizacji)
 12
Author: Ilias Bartolini,
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
2010-02-27 11:45:37

Istnieje bardzo realny problem z bibliotekami współdzielonymi, który idiom pimpl omija dokładnie tak, jak nie potrafią tego zwykli wirtualiści: nie można bezpiecznie modyfikować/usuwać danych klasy bez zmuszania użytkowników klasy do rekompilacji kodu. Może to być dopuszczalne w pewnych okolicznościach, ale nie np. w przypadku bibliotek systemowych.

Aby szczegółowo wyjaśnić problem, rozważ następujący kod w swojej bibliotece współdzielonej / nagłówku:

// header
struct A
{
public:
  A();
  // more public interface, some of which uses the int below
private:
  int a;
};

// library 
A::A()
  : a(0)
{}

Kompilator emituje kod w bibliotece współdzielonej, który oblicza adres liczby całkowitej, która ma być zainicjalizowana jako pewne przesunięcie (prawdopodobnie zero w tym przypadku, ponieważ jest to jedyny element) od wskaźnika do obiektu a, który wie, że jest this.

Po Stronie Użytkownika kodu, new A najpierw przydziela sizeof(A) bajty pamięci, a następnie przekazuje wskaźnik do tej pamięci konstruktorowi A::A() jako this.

Jeśli w późniejszej wersji biblioteki zdecydujesz się upuścić liczbę całkowitą, uczynić ją większą, mniejszą lub dodać członków, pojawi się niedopasowanie między ilością kodu przydzielonego przez użytkownika pamięci a przesunięciami oczekiwanymi przez kod konstruktora. Prawdopodobnym rezultatem jest awaria, jeśli masz szczęście - jeśli masz mniej szczęścia, Twoje oprogramowanie zachowuje się dziwnie.

Przez pimpl ' ING, możesz bezpiecznie dodawać i usuwać elementy składowe danych do klasy wewnętrznej, ponieważ alokacja pamięci i wywołanie konstruktora mają miejsce w bibliotece współdzielonej:

// header
struct A
{
public:
  A();
  // more public interface, all of which delegates to the impl
private:
  void * impl;
};

// library 
A::A()
  : impl(new A_impl())
{}

Wszystko, co musisz teraz zrobić, to zachować publiczny interfejs wolny od elementów danych innych niż wskaźnik do obiekt implementacji, a Ty jesteś bezpieczny od tej klasy błędów.

Edit: powinienem może dodać, że jedynym powodem, dla którego mówię o konstruktorze jest to, że nie chciałem dostarczać więcej kodu - ta sama argumentacja dotyczy wszystkich funkcji, które mają dostęp do elementów danych.

 9
Author: ,
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-05-05 14:59:30

Nienawidzę pryszczy! Robią klasę brzydką i nie do odczytania. Wszystkie metody są przekierowywane na pryszcz. Nigdy nie widzisz w nagłówkach, jakie funkcje ma Klasa, więc nie możesz jej refaktorować(np. po prostu zmienić widoczność metody). Klasa czuje się jak "w ciąży". Myślę, że korzystanie z iterfaces jest lepsze i wystarczająco dużo, aby ukryć implementację przed klientem. Możesz event niech jedna klasa zaimplementuje kilka interfejsów, aby utrzymać je cienkie. Należy preferować interfejsy! Uwaga: nie konieczna jest klasa fabryczna. Istotne jest to, że klienci klasy komunikują się z jej instancjami za pośrednictwem odpowiedniego interfejsu. Ukrywanie prywatnych metod uznaję za dziwną paranoję i nie widzę ku temu powodu, ponieważ mamy do czynienia z

 7
Author: Masha Ananieva,
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
2010-10-05 14:37:06

Nie możemy zapominać, że dziedziczenie jest silniejszym, bliższym powiązaniem niż delegacja. Przy podejmowaniu decyzji, jakie idiomy projektowe zastosować w rozwiązaniu konkretnego problemu, uwzględniłbym również wszystkie kwestie poruszane w udzielonych odpowiedziach.

 5
Author: Sam,
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
2010-02-27 12:37:56

W moim rozumieniu te dwie rzeczy służą zupełnie innym celom. Celem idiomu pryszcz jest w zasadzie dać ci uchwyt do swojej realizacji, dzięki czemu możesz robić rzeczy takie jak szybkie zamiany na rodzaj.

Cel klas wirtualnych jest bardziej zbliżony do celu pozwalającego na polimorfizm, tzn. masz nieznany wskaźnik do obiektu typu pochodnego i kiedy wywołujesz funkcję x, zawsze otrzymujesz właściwą funkcję dla każdej klasy wskaźnik bazowy faktycznie wskazuje za.

Jabłka i pomarańcze naprawdę.

 2
Author: Robert S. Barnes,
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-05-05 14:37:02

Chociaż szeroko omówione w innych odpowiedziach, może mógłbym być nieco bardziej wyraźny o jednej korzyści pimpl nad wirtualnymi klasami bazowymi:

Podejście pimpl jest przezroczyste z punktu widzenia użytkownika, co oznacza, że można np. tworzyć obiekty klasy na stosie i używać ich bezpośrednio w kontenerach. Jeśli spróbujesz ukryć implementację używając abstrakcyjnej wirtualnej klasy bazowej, będziesz musiał zwrócić współdzielony wskaźnik do klasy bazowej z fabryki, komplikując jej użycie. Rozważ następujący równoważny kod klienta:

// Pimpl
Object pi_obj(10);
std::cout << pi_obj.SomeFun1();

std::vector<Object> objs;
objs.emplace_back(3);
objs.emplace_back(4);
objs.emplace_back(5);
for (auto& o : objs)
    std::cout << o.SomeFun1();

// Abstract Base Class
auto abc_obj = ObjectABC::CreateObject(20);
std::cout << abc_obj->SomeFun1();

std::vector<std::shared_ptr<ObjectABC>> objs2;
objs2.push_back(ObjectABC::CreateObject(13));
objs2.push_back(ObjectABC::CreateObject(14));
objs2.push_back(ObjectABC::CreateObject(15));
for (auto& o : objs2)
    std::cout << o->SomeFun1();
 2
Author: Superfly Jon,
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
2017-06-13 14:05:27

Najbardziej irytującym problemem idiomu pimpl jest to, że bardzo utrudnia utrzymanie i analizę istniejącego kodu. Więc używając pimpl płacisz z czasem i frustracją dewelopera tylko po to, aby "zmniejszyć zależności i czasy budowania i zminimalizować ekspozycję nagłówka szczegółów implementacji". Sam zdecyduj, czy naprawdę warto.

Szczególnie "czas budowania" to problem, który można rozwiązać za pomocą lepszego sprzętu lub za pomocą narzędzi takich jak Incredibuild ( www.incredibuild.com ), nie wpływając tym samym na twoje Projektowanie oprogramowania. Projektowanie oprogramowania powinno być zasadniczo niezależne od sposobu jego budowy.

 0
Author: Trantor,
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
2018-04-25 08:18:54