Zastanawiałem się, co skłoniłoby programistę do wybrania albo idiomu Pimpl, albo czystej wirtualnej klasy i dziedziczenia.
Rozumiem, że idiom pimpl ma jedno wyraźne dodatkowe wskazanie dla każdej metody publicznej i narzutu tworzenia obiektów.
Z drugiej strony klasa wirtualna Pure ma niejawne pośrednictwo (vtable) do dziedziczenia implementacji i rozumiem, że nie ma narzutu tworzenia obiektów.
EDYCJA : Ale jeśli tworzysz obiekt z zewnątrz, potrzebujesz fabryki
Co sprawia, że czysta klasa wirtualna jest mniej pożądana niż idiom pimpl?
c++
abstract-class
pimpl-idiom
Arkaitz Jimenez
źródło
źródło
Odpowiedzi:
Pisząc klasę w C ++, warto pomyśleć o tym, czy tak będzie
Typ wartości
Skopiuj według wartości, tożsamość nigdy nie jest ważna. Odpowiednie jest, aby był to klucz w std :: map. Na przykład klasa „ciągowa”, klasa „data” lub klasa „liczby zespolonej”. „Kopiowanie” instancji takiej klasy ma sens.
Typ jednostki
Tożsamość jest ważna. Zawsze przekazywane przez odwołanie, nigdy przez „wartość”. Często nie ma sensu w ogóle „kopiować” instancji klasy. Jeśli ma to sens, zwykle bardziej odpowiednia jest polimorficzna metoda „klonowania”. Przykłady: klasa Socket, klasa bazy danych, klasa „strategii”, wszystko, co byłoby „zamknięciem” w języku funkcjonalnym.
Zarówno pImpl, jak i czysta abstrakcyjna klasa bazowa są technikami zmniejszającymi zależności czasu kompilacji.
Jednak zawsze używam pImpl tylko do implementowania 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 narzutów składniowych, ponieważ musisz napisać metody przekazywania dla wszystkich metod publicznych. W przypadku klas typu 2 zawsze używam czystej abstrakcyjnej klasy bazowej z powiązanymi metodami fabrycznymi.
źródło
Pointer to implementation
polega zwykle na ukryciu szczegółów implementacji strukturalnej.Interfaces
dotyczą tworzenia instancji różnych implementacji. Tak naprawdę służą dwóm różnym celom.źródło
Idiom pimpl pomaga zredukować zależności i czasy kompilacji, szczególnie w dużych aplikacjach, i minimalizuje ekspozycję nagłówka szczegółów implementacji Twojej klasy do jednej jednostki 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 (czyste wirtuale) to coś, o czym Twoi klienci muszą być świadomi: jeśli spróbujesz ich użyć do zredukowania sprzężeń i odwołań cyklicznych, musisz dodać sposób, aby umożliwić im tworzenie obiektów (np. Za pomocą metod lub klas fabrycznych, zastrzyk zależności lub inne mechanizmy).
źródło
Szukałem odpowiedzi na to samo pytanie. Po przeczytaniu kilku artykułów i trochę praktyki wolę używać „czystych wirtualnych interfejsów klas” .
Jedyną wadą ( próbuję to zbadać ) jest to, że idiom pimpl mógłby być szybszy
źródło
Nienawidzę pryszczów! Robią klasę brzydko i nieczytelnie. Wszystkie metody są przekierowywane na pryszcz. Nigdy nie widzisz w nagłówkach, jakie funkcjonalności ma ta klasa, więc nie możesz jej refaktoryzować (np. Po prostu zmienić widoczność metody). Klasa wydaje się być „w ciąży”. Myślę, że używanie iterfaces jest lepsze i naprawdę wystarczające, aby ukryć implementację przed klientem. Możesz pozwolić jednej klasie zaimplementować kilka interfejsów, aby były cienkie. Należy preferować interfejsy! Uwaga: nie jest wymagana klasa fabryczna. Istotne jest to, że klienci klasy komunikują się z jej instancjami za pośrednictwem odpowiedniego interfejsu. Ukrywanie prywatnych metod uważam za dziwną paranoję i nie widzę powodu, ponieważ mamy interfejsy.
źródło
Istnieje bardzo poważny problem z bibliotekami współdzielonymi, który idiom pimpl omija z łatwością, czego nie mogą zrobić zwykli wirtualiści: nie można bezpiecznie modyfikować / usuwać członków danych klasy bez zmuszania użytkowników tej klasy do ponownej kompilacji 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 udostępnianej bibliotece / nagłówku:
Kompilator emituje kod w bibliotece współdzielonej, który oblicza adres liczby całkowitej, która ma być zainicjowana, aby była pewnym przesunięciem (prawdopodobnie w tym przypadku zerem, ponieważ jest to jedyny element członkowski) od wskaźnika do obiektu A, o którym wie, że jest
this
.Po stronie użytkownika kodu, a
new A
najpierw przydzielisizeof(A)
bajty pamięci, a następnie przekaże wskaźnik do tej pamięciA::A()
konstruktorowi asthis
.Jeśli w późniejszej wersji biblioteki zdecydujesz się usunąć liczbę całkowitą, powiększyć ją, pomniejszyć lub dodać składowe, wystąpi niezgodność między ilością pamięci przydzielonej przez użytkownika a przesunięciami oczekiwanymi przez kod konstruktora. Prawdopodobnym rezultatem jest awaria, jeśli masz szczęście - jeśli masz mniej szczęścia, oprogramowanie zachowuje się dziwnie.
Dzięki pimpl'owaniu możesz bezpiecznie dodawać i usuwać składowe danych do klasy wewnętrznej, ponieważ alokacja pamięci i wywołanie konstruktora mają miejsce w bibliotece współdzielonej:
Wszystko, co musisz teraz zrobić, to uwolnić swój publiczny interfejs od członków danych innych niż wskaźnik do obiektu implementacji, a będziesz bezpieczny przed tą klasą błędów.
Edycja: Powinienem dodać, że jedynym powodem, dla którego mówię tutaj o konstruktorze, jest to, że nie chciałem udostępniać więcej kodu - ta sama argumentacja dotyczy wszystkich funkcji, które mają dostęp do członków danych.
źródło
class A_impl *impl_;
Nie wolno nam zapominać, że dziedziczenie jest silniejszym i bliższym związkiem niż delegacja. Chciałbym również wziąć pod uwagę wszystkie kwestie poruszone w udzielonych odpowiedziach przy podejmowaniu decyzji, jakie idiomy projektowe zastosować w rozwiązaniu konkretnego problemu.
źródło
Chociaż szeroko omówione w innych odpowiedziach, być może mogę nieco bardziej jednoznacznie powiedzieć o jednej korzyści z pimpl w porównaniu z wirtualnymi klasami bazowymi:
Podejście pimpl jest przejrzyste 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ę za pomocą abstrakcyjnej wirtualnej klasy bazowej, będziesz musiał zwrócić wspólny wskaźnik do klasy bazowej z fabryki, co komplikuje jej użycie. Rozważ następujący równoważny kod klienta:
źródło
W moim rozumieniu te dwie rzeczy służą zupełnie innym celom. Celem idiomu pryszcz jest w zasadzie dać ci uchwyt do implementacji, dzięki czemu możesz robić takie rzeczy, jak szybkie zamiany na sort.
Cel klas wirtualnych jest bardziej podobny do dopuszczenia polimorfizmu, tj. Masz nieznany wskaźnik do obiektu typu pochodnego, a kiedy wywołujesz funkcję x, zawsze otrzymujesz odpowiednią funkcję dla dowolnej klasy, na którą faktycznie wskazuje wskaźnik bazowy.
Naprawdę jabłka i pomarańcze.
źródło
Najbardziej irytującym problemem związanym z idiomem pimpl jest to, że niezwykle trudno jest utrzymać i analizować istniejący kod. Więc używając pimpl płacisz czasem i frustracją programisty tylko po to, aby „zredukować zależności i czasy kompilacji oraz zminimalizować wyświetlanie szczegółów implementacji w nagłówku”. Zdecyduj sam, czy naprawdę warto.
Zwłaszcza „czasy kompilacji” to problem, który można rozwiązać za pomocą lepszego sprzętu lub narzędzi takich jak Incredibuild (www.incredibuild.com, również zawarte w programie Visual Studio 2017), nie wpływając w ten sposób na projekt oprogramowania. Projekt oprogramowania powinien być zasadniczo niezależny od sposobu, w jaki jest ono zbudowane.
źródło