Inteligentne wskazówki: kto jest właścicielem obiektu? [Zamknięte]

114

C ++ dotyczy własności pamięci - czyli semantyki własności .

Za zwolnienie tej pamięci odpowiada właściciel fragmentu dynamicznie przydzielanej pamięci. Tak więc naprawdę pojawia się pytanie, kto jest właścicielem pamięci.

W C ++ własność jest udokumentowana przez typ, w którym surowy wskaźnik jest zawinięty, więc w dobrym programie (IMO) C ++ bardzo rzadko ( rzadko , nie nigdy ) można zobaczyć surowe wskaźniki przekazywane dookoła (ponieważ surowe wskaźniki nie mają wywiedzionej własności, więc nie mów, kto jest właścicielem pamięci, a zatem bez uważnego przeczytania dokumentacji nie możesz powiedzieć, kto jest odpowiedzialny za własność).

I odwrotnie, rzadko można zobaczyć surowe wskaźniki przechowywane w klasie, każdy surowy wskaźnik jest przechowywany we własnym inteligentnym opakowaniu wskaźnika. ( Uwaga: jeśli nie jesteś właścicielem obiektu, nie powinieneś go przechowywać, ponieważ nie możesz wiedzieć, kiedy wyjdzie on poza zakres i zostanie zniszczony).

Więc pytanie:

  • Z jakim rodzajem semantyki własności ludzie się spotykają?
  • Jakie standardowe klasy są używane do implementacji tej semantyki?
  • W jakich sytuacjach uważasz je za przydatne?

Pozwala zachować 1 typ własności semantycznej na odpowiedź, aby można było indywidualnie głosować w górę iw dół.

Podsumowanie:

Koncepcyjnie inteligentne wskaźniki są proste, a naiwna implementacja jest łatwa. Widziałem wiele prób implementacji, ale niezmiennie są one zepsute w sposób, który nie jest oczywisty dla zwykłego użycia i przykładów. Dlatego radzę zawsze używać dobrze przetestowanych inteligentnych wskaźników z biblioteki zamiast tworzyć własne. std::auto_ptrlub jeden z inteligentnych wskaźników Boost wydaje się spełniać wszystkie moje potrzeby.

std::auto_ptr<T>:

Właścicielem obiektu jest jedna osoba. Przeniesienie własności jest dozwolone.

Użycie: Pozwala to zdefiniować interfejsy, które pokazują wyraźne przeniesienie własności.

boost::scoped_ptr<T>

Właścicielem obiektu jest jedna osoba. Przeniesienie własności NIE jest dozwolone.

Użycie: używane do pokazania wyraźnej własności. Obiekt zostanie zniszczony przez destruktor lub po jawnym zresetowaniu.

boost::shared_ptr<T>( std::tr1::shared_ptr<T>)

Wielokrotna własność. To jest prosty wskaźnik liczony referencyjnie. Gdy liczba referencji osiągnie zero, obiekt zostanie zniszczony.

Użycie: Gdy obiekt może mieć wiele kwiatów, których okres istnienia nie może być określony w czasie kompilacji.

boost::weak_ptr<T>:

Używane shared_ptr<T>w sytuacjach, gdy może wystąpić cykl wskaźników.

Użycie: Służy do zatrzymywania cykli przed zachowywaniem obiektów, gdy tylko cykl utrzymuje wspólne odniesienie.

Martin York
źródło
14
?? Jakie było pytanie?
Pacerier
9
Chciałem tylko zaznaczyć, że od czasu opublikowania tego pytania auto_ptr zostało wycofane na korzyść (teraz znormalizowanego) unique_ptr
Juan Campa
In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good (IMO) Czy można to przeformułować? W ogóle tego nie rozumiem.
lolololol ol
@lololololol Przecinasz zdanie na pół. In C++ ownership is documented by the type a RAW pointer is wrapped inside thus in a good C++ program it is very rare to see RAW pointers passed around. Wskaźniki RAW nie mają semantyki własności. Jeśli nie znasz właściciela, nie wiesz, kto jest odpowiedzialny za usunięcie obiektu.Istnieje kilka standardowych klas, które są używane do zawijania wskaźników (std :: shared_ptr, std :: unique_ptr itp.), Które definiują własność, a tym samym zdefiniuj, kto jest odpowiedzialny za usunięcie wskaźnika.
Martin York,
1
W C ++ 11 + NIE UŻYWAJ auto_ptr! Zamiast tego użyj unique_ptr!
Val mówi Przywróć Monikę

Odpowiedzi:

20

Dla mnie te 3 rodzaje zaspokajają większość moich potrzeb:

shared_ptr - liczone referencyjnie, zwalnianie, gdy licznik osiągnie zero

weak_ptr- tak samo jak powyżej, ale jest to „niewolnik” dla a shared_ptr, nie można cofnąć przydziału

auto_ptr- gdy tworzenie i cofanie przydziału ma miejsce w ramach tej samej funkcji lub gdy obiekt należy traktować jako jedynego właściciela zawsze. Kiedy przypisujesz jeden wskaźnik do drugiego, drugi „kradnie” obiekt z pierwszego.

Mam na to własną implementację, ale są one również dostępne w Boost.

Nadal przekazuję obiekty przez odniesienie ( constjeśli to możliwe), w tym przypadku wywołana metoda musi zakładać, że obiekt żyje tylko w czasie wywołania.

Używam innego rodzaju wskaźnika, który nazywam hub_ptr . Dzieje się tak, gdy masz obiekt, który musi być dostępny z obiektów w nim zagnieżdżonych (zwykle jako wirtualna klasa bazowa). Można to rozwiązać, przekazując weak_ptrim a, ale samo to nie ma shared_ptr. Ponieważ wie, że te obiekty nie żyłyby dłużej niż on, przekazuje im hub_ptr (to po prostu opakowanie szablonu do zwykłego wskaźnika).

Fabio Ceconello
źródło
2
Zamiast tworzyć własną klasę wskaźników (hub_ptr), dlaczego po prostu nie przekażesz * tego do tych obiektów i nie pozwolisz im przechowywać tego jako odniesienia? Ponieważ nawet przyznajesz, że obiekty zostaną zniszczone w tym samym czasie, co klasa będąca ich właścicielem, nie rozumiem sensu przeskakiwania przez tak wiele obręczy.
Michel
4
Zasadniczo jest to umowa na projekt, aby wszystko było jasne. Gdy obiekt podrzędny otrzymuje hub_ptr, wie, że wskazany obiekt nie zostanie zniszczony za życia dziecka i nie ma do niego prawa własności. Zarówno obiekty zawarte, jak i kontenery są zgodne z jasnym zestawem reguł. Jeśli używasz czystego wskaźnika, reguły mogą być udokumentowane, ale nie będą egzekwowane przez kompilator i kod.
Fabio Ceconello
1
Zauważ również, że możesz mieć #ifdefs, aby hub_ptr był wpisywany do nagiego wskaźnika w kompilacjach wydania, więc narzut będzie istniał tylko w kompilacji debugowania.
Fabio Ceconello
3
Zauważ, że dokumentacja Boost jest sprzeczna z twoim opisem scoped_ptr. Stwierdza, że ​​tak jest noncopyablei że prawa własności nie można przenieść.
Alec Thomas
3
@Alec Thomas, masz rację. Myślałem o auto_ptr i napisałem scoped_ptr. Poprawione.
Fabio Ceconello
23

Prosty model C ++

W większości modułów, które widziałem, domyślnie zakładano, że otrzymywanie wskaźników nie otrzymuje własności. W rzeczywistości funkcje / metody rezygnujące z posiadania wskaźnika były zarówno bardzo rzadkie, jak i wyraźnie wyrażono ten fakt w swojej dokumentacji.

Ten model zakłada, że ​​użytkownik jest właścicielem tylko tego, co wyraźnie przydzieli . Cała reszta jest automatycznie usuwana (przy wyjściu zakresu lub przez RAII). Jest to model podobny do C, rozszerzony przez fakt, że większość wskaźników jest własnością obiektów, które zwalniają je automatycznie lub w razie potrzeby (głównie przy zniszczeniu wspomnianych obiektów), a długość życia obiektów jest przewidywalna (RAII jest twoim przyjacielem, jeszcze raz).

W tym modelu surowe wskaźniki krążą swobodnie i przeważnie nie są niebezpieczne (ale jeśli programista jest wystarczająco inteligentny, użyje zamiast tego referencji, gdy tylko będzie to możliwe).

  • surowe wskaźniki
  • std :: auto_ptr
  • boost :: scoped_ptr

Model Smart Pointed C ++

W kodzie pełnym inteligentnych wskaźników użytkownik może mieć nadzieję, że zignoruje żywotność obiektów. Właścicielem nigdy nie jest kod użytkownika: jest to sam inteligentny wskaźnik (znowu RAII). Problem polega na tym, że odwołania cykliczne zmieszane z inteligentnymi wskaźnikami liczącymi referencje mogą być śmiertelne , więc musisz radzić sobie zarówno ze wspólnymi wskaźnikami, jak i słabymi wskaźnikami. Więc nadal masz własność do rozważenia (słaby wskaźnik może wskazywać na nic, nawet jeśli jego przewaga nad surowym wskaźnikiem polega na tym, że może ci to powiedzieć).

  • boost :: shared_ptr
  • boost :: słaby_ptr

Wniosek

Bez względu na modele, które opisuję, chyba że wyjątek, otrzymanie wskaźnika nie oznacza otrzymania jego własności i nadal bardzo ważne jest, aby wiedzieć, kto jest właścicielem . Nawet w przypadku kodu C ++ w dużym stopniu wykorzystującego odwołania i / lub inteligentne wskaźniki.

paercebal
źródło
10

Nie mają współwłasności. Jeśli tak, upewnij się, że jest to kod, nad którym nie masz kontroli.

To rozwiązuje 100% problemów, ponieważ zmusza cię do zrozumienia, jak wszystko na siebie oddziałuje.

MSN
źródło
2
  • Współwłasność
  • boost :: shared_ptr

Gdy zasób jest współdzielony między wieloma obiektami. Wzmocnienie shared_ptr korzysta z liczenia odwołań, aby upewnić się, że zasób zostanie cofnięty, gdy wszyscy zostaną skończeni.

Martin York
źródło
2

std::tr1::shared_ptr<Blah> jest często najlepszym rozwiązaniem.

Matt Cruikshank
źródło
2
shared_ptr jest najczęściej. Ale jest ich znacznie więcej. Każdy ma swój własny wzorzec użytkowania oraz dobre i złe miejsca do pozywania. Przydałby się nieco dokładniejszy opis.
Martin York,
Jeśli utkniesz ze starszym kompilatorem, boost :: shared_ptr <blah> jest tym, na czym opiera się std :: tr1 :: shared_ptr <blah>. Jest to na tyle prosta klasa, że ​​prawdopodobnie możesz ją zgrać z Boost i używać, nawet jeśli Twój kompilator nie jest obsługiwany przez najnowszą wersję Boost.
Branan,
2

Od boostu jest też biblioteka kontenerów wskaźników . Są one nieco wydajniejsze i łatwiejsze w użyciu niż standardowy kontener inteligentnych wskaźników, jeśli będziesz używać obiektów tylko w kontekście ich kontenera.

W systemie Windows istnieją wskaźniki COM (IUnknown, IDispatch i friends) oraz różne inteligentne wskaźniki do ich obsługi (np. CComPtr ATL i inteligentne wskaźniki generowane automatycznie przez instrukcję „import” w programie Visual Studio na podstawie klasy _com_ptr ).

Ryan Ginstrom
źródło
1
  • Jeden właściciel
  • boost :: scoped_ptr

Gdy potrzebujesz dynamicznie alokować pamięć, ale chcesz mieć pewność, że zostanie ona zwolniona w każdym punkcie wyjścia z bloku.

Uważam to za przydatne, ponieważ można go łatwo ponownie osadzić i zwolnić bez martwienia się o wyciek

Pieter
źródło
1

Nie sądzę, żebym kiedykolwiek mógł mieć współwłasność w moim projekcie. Właściwie z czubka głowy jedyny ważny przypadek, jaki przychodzi mi do głowy, to wzór wagi muszej.

Nemanja Trifunovic
źródło
1

yasper :: ptr to lekka alternatywa podobna do boost :: shared_ptr. Dobrze sprawdza się w moim (na razie) małym projekcie.

Na stronie internetowej http://yasper.sourceforge.net/ jest to opisane w następujący sposób:

Po co pisać kolejny inteligentny wskaźnik C ++? Istnieje już kilka wysokiej jakości implementacji inteligentnych wskaźników dla C ++, w szczególności panteon wskaźników Boost i SmartPtr Lokiego. Aby uzyskać dobre porównanie implementacji inteligentnych wskaźników i kiedy ich użycie jest właściwe, przeczytaj artykuł Herba Suttera The New C ++: Smart (er) Pointers. W przeciwieństwie do rozbudowanych funkcji innych bibliotek, Yasper jest wąskim wskaźnikiem zliczania referencji. Ściśle koresponduje z zasadami shared_ptr Boost i Lokiego RefCounted / AllowConversion. Yasper pozwala programistom C ++ zapomnieć o zarządzaniu pamięcią bez wprowadzania dużych zależności Boost lub konieczności poznawania skomplikowanych szablonów zasad Lokiego. Filozofia

* small (contained in single header)
* simple (nothing fancy in the code, easy to understand)
* maximum compatibility (drop in replacement for dumb pointers)

Ostatni punkt może być niebezpieczny, ponieważ yasper pozwala na ryzykowne (ale użyteczne) działania (takie jak przypisanie do surowych wskaźników i ręczne zwolnienie) niedozwolone przez inne implementacje. Uważaj, używaj tych funkcji tylko wtedy, gdy wiesz, co robisz!

Hernán
źródło
1

Istnieje inna często używana forma pojedynczego-przenoszalnego właściciela, która jest lepsza, auto_ptrponieważ pozwala uniknąć problemów spowodowanych przez auto_ptrszalone zepsucie semantyki przypisania.

Nie mówię o nikim innym niż swap. Każdy typ z odpowiednią swapfunkcją może być pomyślany jako inteligentne odniesienie do jakiejś treści, której jest właścicielem do czasu przeniesienia własności do innej instancji tego samego typu, poprzez ich zamianę. Każda instancja zachowuje swoją tożsamość, ale zostaje powiązana z nową zawartością. To jak bezpieczne odniesienie do ponownego powiązania.

(Jest to raczej inteligentne odniesienie niż inteligentny wskaźnik, ponieważ nie musisz jawnie usuwać go, aby uzyskać dostęp do treści).

Oznacza to, że auto_ptr staje się mniej potrzebne - jest potrzebne tylko do wypełnienia luk, w których typy nie mają dobrej swapfunkcji. Ale wszystkie kontenery standardowe tak.

Daniel Earwicker
źródło
Może staje się mniej konieczne (powiedziałbym, że scoped_ptr sprawia, że ​​jest mniej konieczne niż to), ale nie zniknie. Posiadanie funkcji zamiany w ogóle ci nie pomaga, jeśli przydzielisz coś na stercie i ktoś wyrzuci, zanim to usuniesz, lub po prostu zapomnisz.
Michel
Dokładnie to powiedziałem w ostatnim akapicie.
Daniel Earwicker
0
  • Jeden właściciel: Aka usuń przy kopiowaniu
  • std :: auto_ptr

Gdy twórca obiektu chce jawnie przekazać własność komuś innemu. Jest to również sposób dokumentowania w kodzie, który ci to daję i nie śledzę go już, więc upewnij się, że usunąłeś go po zakończeniu.

Martin York
źródło