Porównania, zalety, wady i kiedy używać?
Jest to spin-off z wątku usuwania śmieci, w którym to, co uważałem za prostą odpowiedź, wygenerowało wiele komentarzy na temat niektórych konkretnych implementacji inteligentnych wskaźników, więc wydawało się, że warto rozpocząć nowy post.
Ostatecznie pytanie brzmi, jakie są różne implementacje inteligentnych wskaźników w C ++ i jak się one porównują? Tylko proste plusy i minusy lub wyjątki i pułapki związane z czymś, co w innym przypadku mogłoby się sprawdzić.
Opublikowałem kilka implementacji, których użyłem lub przynajmniej przerzuciłem i rozważałem użycie jako odpowiedzi poniżej oraz moje zrozumienie ich różnic i podobieństw, które mogą nie być w 100% dokładne, więc nie krępuj się, aby sprawdzić lub poprawić mnie w razie potrzeby.
Celem jest poznanie niektórych nowych obiektów i bibliotek lub poprawienie mojego wykorzystania i zrozumienia istniejących wdrożeń, które są już szeroko stosowane, i otrzymanie przyzwoitego odniesienia dla innych.
źródło
osg::ref_ptr
.std::auto_ptr
.std::auto_ptr_ref
jest szczegółem projektustd::auto_ptr
.std::auto_ptr
nie ma nic wspólnego z wyrzucaniem elementów bezużytecznych, jego głównym celem jest w szczególności umożliwienie bezpiecznego przeniesienia własności, szczególnie w sytuacjach wywołań funkcji i ich zwracania.std::unique_ptr
może rozwiązać tylko "problemy", które przytaczasz za pomocą standardowych kontenerów, ponieważ C ++ zmienił się, aby umożliwić rozróżnienie między przenoszeniem i kopiowaniem, a standardowe kontenery zmieniły się, aby to wykorzystać.auto_ptr_ref
szczegółów implementacji). Mimo to zgadzam się, że powinieneś zamieścić to jako odpowiedź i przeformułować pytanie tak, aby było pytaniem faktycznym. Może to następnie służyć jako odniesienie w przyszłości.Odpowiedzi:
C ++ 03
std::auto_ptr
- Być może jeden z oryginałów, który cierpiał na syndrom pierwszego przeciągu, zapewniał tylko ograniczone możliwości wywozu śmieci. Pierwszą wadą jest to, że wywołujedelete
zniszczenie, co czyni je niedopuszczalnymi do przechowywania obiektów przydzielonych do tablicy (new[]
). Przejmuje na własność wskaźnik, więc dwa automatyczne wskaźniki nie powinny zawierać tego samego obiektu. Przypisanie spowoduje przeniesienie własności i zresetuje automatyczny wskaźnik rvalue do pustego wskaźnika. Co prowadzi do prawdopodobnie najgorszej wady; nie można ich używać w kontenerach STL ze względu na wspomnianą powyżej niemożność kopiowania. Ostatnim ciosem dla każdego przypadku użycia jest to, że zostaną one uznane za przestarzałe w następnym standardzie C ++.std::auto_ptr_ref
- To nie jest inteligentny wskaźnik, w rzeczywistości jest to szczegół projektowy używany w połączeniu zstd::auto_ptr
możliwością kopiowania i przypisywania w określonych sytuacjach. W szczególności można go użyć do konwersji wartości innej niżstd::auto_ptr
stała na lwartość przy użyciu sztuczki Colvin-Gibbons, znanej również jako konstruktor przenoszenia, aby przenieść własność.Wręcz przeciwnie, być może
std::auto_ptr
tak naprawdę nie był przeznaczony do stosowania jako inteligentny wskaźnik ogólnego przeznaczenia do automatycznego czyszczenia pamięci. Większość mojego ograniczonego rozumienia i przypuszczeń opiera się na efektywnym użyciu auto_ptr Herba Suttera i używam go regularnie, chociaż nie zawsze w najbardziej zoptymalizowany sposób.C ++ 11
std::unique_ptr
- To nasz przyjaciel, który będzie zastąpieniestd::auto_ptr
będzie bardzo podobny z wyjątkiem najważniejszych ulepszeń w celu skorygowania słabościstd::auto_ptr
jak praca z tablicami, lwartości ochrony poprzez prywatnego konstruktora kopiowania, użytkować z kontenerów STL i algorytmów itp Ponieważ jest to wydajność napowietrznych a ślad pamięciowy jest ograniczony, jest to idealny kandydat do zastąpienia, lub być może trafniej opisanego jako posiadanie, surowych wskaźników. Jak sugeruje „unikalny”, istnieje tylko jeden właściciel wskaźnika, tak jak poprzednistd::auto_ptr
.std::shared_ptr
- Uważam, że jest to oparte na TR1 iboost::shared_ptr
ulepszone, aby uwzględnić również aliasing i arytmetykę wskaźnika. Krótko mówiąc, otacza on liczony w referencjach inteligentny wskaźnik wokół dynamicznie przydzielonego obiektu. Ponieważ „udostępniony” oznacza, że wskaźnik może być własnością więcej niż jednego wskaźnika współdzielonego, gdy ostatnie odwołanie do ostatniego wskaźnika udostępnionego wychodzi poza zakres, wówczas obiekt zostanie odpowiednio usunięty. Są one również bezpieczne dla wątków i w większości przypadków mogą obsługiwać niekompletne typy.std::make_shared
może służyć do wydajnego konstruowaniastd::shared_ptr
alokacji z jedną stertą przy użyciu domyślnego alokatora.std::weak_ptr
- Podobnie w oparciu o TR1 iboost::weak_ptr
. Jest to odwołanie do obiektu należącego do astd::shared_ptr
i dlatego nie zapobiegnie usunięciu obiektu, jeślistd::shared_ptr
liczba odwołań spadnie do zera. Aby uzyskać dostęp do surowego wskaźnika, musisz najpierw uzyskać dostęp dostd::shared_ptr
przez wywołanie,lock
które zwróci wartość pustą,std::shared_ptr
jeśli posiadany wskaźnik wygasł i został już zniszczony. Jest to przydatne przede wszystkim w celu uniknięcia nieskończonej liczby zawieszonych odwołań podczas korzystania z wielu inteligentnych wskaźników.Podnieść
boost::shared_ptr
- Prawdopodobnie najłatwiejszy w użyciu w różnych scenariuszach (STL, PIMPL, RAII itp.) Jest to wspólny inteligentny wskaźnik liczony z odniesieniami. Słyszałem kilka skarg dotyczących wydajności i kosztów ogólnych w niektórych sytuacjach, ale musiałem je zignorować, ponieważ nie pamiętam, o co chodzi. Najwyraźniej był na tyle popularny, że stał się oczekującym standardowym obiektem C ++ i nie przychodzą na myśl żadne wady w stosunku do normy dotyczącej inteligentnych wskaźników.boost::weak_ptr
- Podobnie jak w poprzednim opisiestd::weak_ptr
, w oparciu o tę implementację, umożliwia to odniesienie do plikuboost::shared_ptr
. Nic dziwnego, że wywołujeszlock()
dostęp do „silnego” wskaźnika współdzielonego i musisz sprawdzić, czy jest on prawidłowy, ponieważ mógł już zostać zniszczony. Po prostu upewnij się, że nie przechowujesz zwróconego wskaźnika udostępnionego i pozwól mu wyjść poza zakres, gdy tylko skończysz z nim, w przeciwnym razie wrócisz do problemu z cyklicznymi odwołaniami, w którym liczba odwołań się zawiesi, a obiekty nie zostaną zniszczone.boost::scoped_ptr
- Jest to prosta, inteligentna klasa wskaźnika z niewielkim narzutem, prawdopodobnie zaprojektowana jako lepsza alternatywa,boost::shared_ptr
gdy jest użyteczna. Jest to porównywalnestd::auto_ptr
zwłaszcza z tym, że nie można go bezpiecznie używać jako elementu kontenera STL lub z wieloma wskaźnikami do tego samego obiektu.boost::intrusive_ptr
- Nigdy tego nie używałem, ale z mojego zrozumienia jest to przeznaczone do użycia podczas tworzenia własnych klas kompatybilnych z inteligentnymi wskaźnikami. Musisz sam zaimplementować liczenie odwołań, musisz również zaimplementować kilka metod, jeśli chcesz, aby Twoja klasa była ogólna, a ponadto musisz zaimplementować własne zabezpieczenie wątków. Z drugiej strony prawdopodobnie daje to najbardziej niestandardowy sposób wybierania i wybierania dokładnie tego, ile lub jak mało „sprytu” chcesz.intrusive_ptr
jest zazwyczaj bardziej wydajne niż,shared_ptr
ponieważ umożliwia przydzielenie jednej sterty na obiekt. (dzięki Arvid)boost::shared_array
- To jestboost::shared_ptr
dla tablic. Zasadniczonew []
,operator[]
i oczywiściedelete []
są zapiekane. To może być używane w pojemnikach STL i o ile wiem, robi wszystkoboost:shared_ptr
, chociaż nie możnaboost::weak_ptr
z nimi używać . Możesz jednak alternatywnie użyćboost::shared_ptr<std::vector<>>
do podobnej funkcji i odzyskać możliwość korzystaniaboost::weak_ptr
z referencji.boost::scoped_array
- To jestboost::scoped_ptr
dla tablic. Podobnie jak w przypadkuboost::shared_array
wszystkich niezbędnych dobrodziejstw tablicy, ta jest nie do kopiowania i dlatego nie może być używana w kontenerach STL. Znalazłem prawie wszędzie, gdzie chcesz tego użyć, prawdopodobnie możesz po prostu użyćstd::vector
. Nigdy nie ustaliłem, która jest w rzeczywistości szybsza lub ma mniejszy narzut, ale ta ograniczona tablica wydaje się znacznie mniej zaangażowana niż wektor STL. Jeśli chcesz zachować alokację na stosie, rozważboost::array
zamiast tego.Qt
QPointer
- Wprowadzony w Qt 4.0 jest to „słaby” inteligentny wskaźnik, który działa tylko zQObject
klasami pochodnymi, co w ramach Qt jest prawie wszystkim, więc nie jest to tak naprawdę ograniczenie. Istnieją jednak ograniczenia, a mianowicie to, że nie dostarcza on „silnego” wskaźnika i chociaż możesz sprawdzić, czy podstawowy obiekt jest prawidłowy,isNull()
możesz znaleźć obiekt, który został zniszczony zaraz po przejściu tego sprawdzenia, szczególnie w środowiskach wielowątkowych. Qt ludzie uważają to za przestarzałe, jak sądzę.QSharedDataPointer
- Jest to „silny” inteligentny wskaźnik, który może być porównywalny,boost::intrusive_ptr
chociaż ma pewne wbudowane zabezpieczenia wątków, ale wymaga uwzględnienia metod zliczania odwołań (ref
ideref
), co można zrobić przez tworzenie podklasQSharedData
. Podobnie jak w przypadku większości Qt, obiekty są najlepiej używane poprzez szerokie dziedziczenie, a tworzenie podklas wydaje się być zgodne z zamierzonym projektem.QExplicitlySharedDataPointer
- Bardzo podobny doQSharedDataPointer
tego, że nie wywołuje niejawniedetach()
. Nazwałbym tę wersję 2.0,QSharedDataPointer
ponieważ ten niewielki wzrost kontroli co do dokładnego momentu odłączenia po spadku liczby referencji do zera nie jest szczególnie warty zupełnie nowego obiektu.QSharedPointer
- Atomowe liczenie odniesień, bezpieczeństwo wątków, wskaźnik współdzielenia, niestandardowe usuwanie (obsługa tablic), brzmi tak, jak wszystko, co powinien być inteligentny wskaźnik. To jest to, czego używam przede wszystkim jako inteligentny wskaźnik w Qt i uważam go za porównywalny,boost:shared_ptr
chociaż prawdopodobnie znacznie bardziej narzut, jak wiele obiektów w Qt.QWeakPointer
- Czy wyczuwasz powtarzający się wzór? Tak jakstd::weak_ptr
iboost::weak_ptr
jest to używane w połączeniu z sytuacją,QSharedPointer
gdy potrzebujesz odwołań między dwoma inteligentnymi wskaźnikami, które w przeciwnym razie spowodowałyby, że obiekty nigdy nie zostałyby usunięte.QScopedPointer
- Ta nazwa powinna również wyglądać znajomo i faktycznie była oparta naboost::scoped_ptr
odmiennych wersjach Qt wspólnych i słabych wskaźników. Działa w celu zapewnienia inteligentnego wskaźnika dla pojedynczego właściciela, bez narzutu,QSharedPointer
który sprawia, że jest bardziej odpowiedni dla zgodności, kodu bezpiecznego dla wyjątków i wszystkich rzeczy, których możesz użyćstd::auto_ptr
lubboost::scoped_ptr
do których.źródło
intrusive_ptr
jest zwykle bardziej wydajne niżshared_ptr
, ponieważ pozwala na przydzielenie jednej sterty na obiekt.shared_ptr
w ogólnym przypadku przydzieli oddzielny obiekt małej sterty dla liczników referencyjnych.std::make_shared
można wykorzystać, aby uzyskać to, co najlepsze z obu światów.shared_ptr
z tylko jednym przydziałem sterty.shared_ptr
s? (Nie licząc rozwiązywania cyklicznych odniesień)Istnieje również Loki, który wdraża inteligentne wskaźniki oparte na polityce.
Inne odniesienia do inteligentnych wskaźników opartych na polityce, odnoszące się do problemu słabego wsparcia optymalizacji pustej bazy wraz z wielokrotnym dziedziczeniem przez wiele kompilatorów:
źródło
Oprócz podanych, istnieją również te zorientowane na bezpieczeństwo:
SaferCPlusPlus
mse::TRefCountingPointer
jest inteligentnym wskaźnikiem liczącym referencje, takim jakstd::shared_ptr
. Różnica polega na tym, żemse::TRefCountingPointer
jest bezpieczniejszy, mniejszy i szybszy, ale nie ma żadnego mechanizmu zabezpieczającego gwint. Występuje w wersjach „niepustych” i „naprawionych” (bez możliwości ponownego kierowania), które można bezpiecznie założyć, że zawsze wskazują na prawidłowo przydzielony obiekt. Zasadniczo, jeśli obiekt docelowy jest współdzielony między wątkami asynchronicznymi, użyjstd::shared_ptr
, w przeciwnym raziemse::TRefCountingPointer
jest bardziej optymalny.mse::TScopeOwnerPointer
jest podobny doboost::scoped_ptr
, ale działa w połączeniu zmse::TScopeFixedPointer
w relacji wskaźnika „silny-słaby”, podobnie jakstd::shared_ptr
istd::weak_ptr
.mse::TScopeFixedPointer
wskazuje na obiekty, które są przydzielone na stosie lub których wskaźnik „posiadania” jest na stosie przydzielony. Jest (celowo) ograniczona w swojej funkcjonalności, aby zwiększyć bezpieczeństwo w czasie kompilacji bez kosztów wykonywania. Celem wskaźników „zakresu” jest zasadniczo zidentyfikowanie zestawu okoliczności, które są na tyle proste i deterministyczne, że żadne mechanizmy bezpieczeństwa (w czasie wykonywania) nie są konieczne.mse::TRegisteredPointer
zachowuje się jak surowy wskaźnik, z tą różnicą, że jego wartość jest automatycznie ustawiana na null_ptr, gdy obiekt docelowy zostanie zniszczony. W większości sytuacji może być używany jako ogólny zamiennik surowych wskaźników. Podobnie jak surowy wskaźnik, nie ma żadnego wewnętrznego bezpieczeństwa wątków. Ale w zamian nie ma problemu z celowaniem w obiekty przydzielone na stosie (i uzyskaniem odpowiedniej korzyści z wydajności). Gdy kontrole w czasie wykonywania są włączone, ten wskaźnik jest bezpieczny przed dostępem do nieprawidłowej pamięci. Ponieważmse::TRegisteredPointer
zachowuje się tak samo jak nieprzetworzony wskaźnik wskazujący na prawidłowe obiekty, można go „wyłączyć” (automatycznie zastąpić odpowiednim surowym wskaźnikiem) za pomocą dyrektywy kompilującej w czasie kompilacji, dzięki czemu można go używać do wychwytywania błędów w debugowaniu / testowaniu / beta bez ponoszenia kosztów ogólnych w trybie wydania.Oto artykuł opisujący, dlaczego i jak ich używać. (Uwaga, bezwstydna wtyczka.)
źródło