Jakie implementacje C ++ Smart Pointer są dostępne?

121

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.

AJG85
źródło
5
Myślę, że powinno to zostać ponownie opublikowane jako odpowiedź na to pytanie, a pytanie to powinno zostać przekształcone w rzeczywiste pytanie. W przeciwnym razie czuję, że ludzie zakończą to jako „nie jest to prawdziwe pytanie”.
strager
3
Istnieje wiele innych inteligentnych wskaźników, np . Inteligentne wskaźniki ATL lub OpenSceneGraphosg::ref_ptr .
James McNellis,
11
Czy jest tu pytanie?
Cody Grey
6
Myślę, że źle zrozumiałeś std::auto_ptr. std::auto_ptr_refjest szczegółem projektu std::auto_ptr. std::auto_ptrnie 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_ptrmoż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ć.
CB Bailey,
3
Mówisz, że nie jesteś ekspertem w inteligentnych wskazówkach, ale twoje podsumowanie jest dość wyczerpujące i poprawne (z wyjątkiem drobnego sporu dotyczącego auto_ptr_refszczegółó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.
Konrad Rudolph

Odpowiedzi:

231

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łuje deletezniszczenie, 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 z std::auto_ptrmoż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_ptrstał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_ptrtak 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ąpienie std::auto_ptrbędzie bardzo podobny z wyjątkiem najważniejszych ulepszeń w celu skorygowania słabości std::auto_ptrjak 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 poprzedni std::auto_ptr.

std::shared_ptr- Uważam, że jest to oparte na TR1 i boost::shared_ptrulepszone, 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_sharedmoże służyć do wydajnego konstruowania std::shared_ptralokacji z jedną stertą przy użyciu domyślnego alokatora.

std::weak_ptr- Podobnie w oparciu o TR1 i boost::weak_ptr. Jest to odwołanie do obiektu należącego do a std::shared_ptri dlatego nie zapobiegnie usunięciu obiektu, jeśli std::shared_ptrliczba odwołań spadnie do zera. Aby uzyskać dostęp do surowego wskaźnika, musisz najpierw uzyskać dostęp do std::shared_ptrprzez wywołanie, lockktóre zwróci wartość pustą, std::shared_ptrjeś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 opisie std::weak_ptr, w oparciu o tę implementację, umożliwia to odniesienie do pliku boost::shared_ptr. Nic dziwnego, że wywołujesz lock()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_ptrgdy jest użyteczna. Jest to porównywalne std::auto_ptrzwł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_ptrjest zazwyczaj bardziej wydajne niż, shared_ptrponieważ umożliwia przydzielenie jednej sterty na obiekt. (dzięki Arvid)

boost::shared_array- To jest boost::shared_ptrdla tablic. Zasadniczo new [], operator[]i oczywiście delete []są zapiekane. To może być używane w pojemnikach STL i o ile wiem, robi wszystko boost:shared_ptr, chociaż nie można boost::weak_ptrz nimi używać . Możesz jednak alternatywnie użyć boost::shared_ptr<std::vector<>>do podobnej funkcji i odzyskać możliwość korzystania boost::weak_ptrz referencji.

boost::scoped_array- To jest boost::scoped_ptrdla tablic. Podobnie jak w przypadku boost::shared_arraywszystkich 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::arrayzamiast tego.


Qt

QPointer- Wprowadzony w Qt 4.0 jest to „słaby” inteligentny wskaźnik, który działa tylko z QObjectklasami 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_ptrchociaż ma pewne wbudowane zabezpieczenia wątków, ale wymaga uwzględnienia metod zliczania odwołań ( refi deref), co można zrobić przez tworzenie podklas QSharedData. 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 do QSharedDataPointertego, że nie wywołuje niejawnie detach(). Nazwałbym tę wersję 2.0, QSharedDataPointerponieważ 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_ptrchociaż prawdopodobnie znacznie bardziej narzut, jak wiele obiektów w Qt.

QWeakPointer- Czy wyczuwasz powtarzający się wzór? Tak jak std::weak_ptri boost::weak_ptrjest to używane w połączeniu z sytuacją, QSharedPointergdy 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 na boost::scoped_ptrodmiennych 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, QSharedPointerktó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_ptrlub boost::scoped_ptrdo których.

AJG85
źródło
1
Myślę, że warto wspomnieć o dwóch rzeczach: intrusive_ptrjest zwykle bardziej wydajne niż shared_ptr, ponieważ pozwala na przydzielenie jednej sterty na obiekt. shared_ptrw ogólnym przypadku przydzieli oddzielny obiekt małej sterty dla liczników referencyjnych. std::make_sharedmożna wykorzystać, aby uzyskać to, co najlepsze z obu światów. shared_ptrz tylko jednym przydziałem sterty.
Arvid,
Mam być może niepowiązane pytanie: czy wyrzucanie elementów bezużytecznych można zaimplementować, zastępując wszystkie wskaźniki przez shared_ptrs? (Nie licząc rozwiązywania cyklicznych odniesień)
Seth Carnegie,
@Seth Carnegie: Nie wszystkie wskazówki będą wskazywały na coś przydzielonego w darmowym sklepie.
In silico
2
@the_mandrill Ale to działa, jeśli destruktor klasy będącej właścicielem jest zdefiniowany w oddzielnej jednostce tłumaczeniowej (plik .cpp) niż kod klienta, który i tak jest podany w idiomie Pimpl. Ponieważ ta jednostka tłumaczeniowa zwykle zna pełną definicję Pimpl i dlatego jej destruktor (kiedy niszczy auto_ptr) poprawnie niszczy Pimpl. Obawiałem się też tego, gdy zobaczyłem te ostrzeżenia, ale spróbowałem i działa (wywoływany jest destruktor Pimpl). PS .: proszę użyć @ -syntax, aby zobaczyć odpowiedzi.
Christian Rau
2
Użyteczność listy została zwiększona poprzez dodanie odpowiednich linków do dokumentów.
ulidtko
5

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:

Gregory Pakosz
źródło
1

Oprócz podanych, istnieją również te zorientowane na bezpieczeństwo:

SaferCPlusPlus

mse::TRefCountingPointerjest inteligentnym wskaźnikiem liczącym referencje, takim jak std::shared_ptr. Różnica polega na tym, że mse::TRefCountingPointerjest 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żyj std::shared_ptr, w przeciwnym razie mse::TRefCountingPointerjest bardziej optymalny.

mse::TScopeOwnerPointerjest podobny do boost::scoped_ptr, ale działa w połączeniu z mse::TScopeFixedPointerw relacji wskaźnika „silny-słaby”, podobnie jak std::shared_ptri std::weak_ptr.

mse::TScopeFixedPointerwskazuje 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::TRegisteredPointerzachowuje 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::TRegisteredPointerzachowuje 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.)

Noe
źródło