Zacząłem studiować inteligentne wskaźniki C ++ 11 i nie widzę pożytecznego zastosowania std::weak_ptr
. Czy ktoś może mi powiedzieć, kiedy std::weak_ptr
jest przydatny / konieczny?
c++11
shared-ptr
weak-ptr
c++-faq
artm
źródło
źródło
Odpowiedzi:
Dobrym przykładem może być pamięć podręczna.
W przypadku obiektów, do których ostatnio uzyskiwano dostęp, chcesz je zachować w pamięci, więc trzymaj do nich silny wskaźnik. Okresowo skanujesz pamięć podręczną i decydujesz, które obiekty nie były ostatnio dostępne. Nie musisz trzymać ich w pamięci, więc pozbywasz się mocnego wskaźnika.
Ale co, jeśli ten obiekt jest używany, a jakiś inny kod zawiera silny wskaźnik do niego? Jeśli pamięć podręczna pozbywa się swojego jedynego wskaźnika do obiektu, nigdy więcej go nie znajdzie. Pamięć podręczna utrzymuje słaby wskaźnik do obiektów, które musi znaleźć, jeśli zdołają pozostać w pamięci.
To właśnie robi słaby wskaźnik - pozwala zlokalizować obiekt, jeśli nadal jest w pobliżu, ale nie utrzymuje go, jeśli nic więcej go nie potrzebuje.
źródło
std::weak_ptr
to bardzo dobry sposób na rozwiązanie problemu wiszących wskaźników . Za pomocą surowych wskaźników nie można ustalić, czy dane, do których się odwołano, zostały zwolnione, czy nie. Zamiast tego, pozwalającstd::shared_ptr
zarządzać danymi i dostarczającstd::weak_ptr
użytkownikom danych, użytkownicy mogą sprawdzić ważność danych, dzwoniącexpired()
lublock()
.Nie można tego zrobić
std::shared_ptr
samemu, ponieważ wszystkiestd::shared_ptr
instancje dzielą własność danych, które nie są usuwane przed usunięciem wszystkich instancjistd::shared_ptr
. Oto przykład sprawdzania zwisającego wskaźnika za pomocąlock()
:źródło
std::weak_ptr::lock
tworzy nowy,std::shared_ptr
który dzieli własność zarządzanego obiektu.Kolejna odpowiedź, miejmy nadzieję, prostsza. (dla innych pracowników Google)
Załóżmy, że masz
Team
iMember
przedmioty.Oczywiście jest to związek:
Team
obiekt będzie miał do niego wskaźnikiMembers
. I prawdopodobne jest, że członkowie będą mieli także wskaźnik cofania do swojegoTeam
obiektu.Następnie masz cykl zależności. Jeśli użyjesz
shared_ptr
, obiekty nie będą już automatycznie uwalniane, gdy porzucisz na nich odniesienia, ponieważ odwołują się do siebie cyklicznie. To jest wyciek pamięci.Łamiesz to, używając
weak_ptr
. „Właściciel” zwykle używa,shared_ptr
a „własność” używaweak_ptr
do swojego rodzica i konwertuje go tymczasowo na,shared_ptr
kiedy potrzebuje dostępu do swojego rodzica.Przechowuj słaby ptr:
w razie potrzeby użyj go
źródło
shared_ptr
to, aby współdzielić własność, więc nikt nie ponosi szczególnej odpowiedzialności za zwolnienie pamięci, jest ona zwalniana automatycznie, gdy nie jest już używana. Chyba że istnieje pętla ... Możesz mieć kilka drużyn dzielących tego samego gracza (poprzednie drużyny?). Jeśli obiekt zespołu „jest właścicielem” członków, nie ma potrzeby używaniashared_ptr
znaku „na początek”.shared_ptr
określany przez „członków zespołu”, kiedy zostanie zniszczony? Opisujesz przypadek, w którym nie ma pętli.Oto jeden przykład podany przez @jleahy: Załóżmy, że masz kolekcję zadań wykonanych asynchronicznie i zarządzanych przez
std::shared_ptr<Task>
. Możesz okresowo robić coś z tymi zadaniami, więc zdarzenie timera może przejść przez astd::vector<std::weak_ptr<Task>>
i dać zadania do zrobienia. Jednak jednocześnie zadanie mogło jednocześnie zdecydować, że nie jest już potrzebne i umrze. Licznik czasu może zatem sprawdzić, czy zadanie nadal żyje, tworząc wspólny wskaźnik ze słabego wskaźnika i używając tego wspólnego wskaźnika, pod warunkiem, że nie jest on zerowy.źródło
Przydają się przy Boost.Asio, gdy nie ma gwarancji, że obiekt docelowy nadal istnieje, gdy wywoływana jest asynchroniczna procedura obsługi. Sztuczka polega na powiązaniu
weak_ptr
obiektu asynchonicznego obiektu obsługi za pomocąstd::bind
przechwytywania lub lambda.Jest to wariant
self = shared_from_this()
idiomu często spotykanego w przykładach Boost.Asio, w którym oczekujący asynchroniczny moduł obsługi nie wydłuży żywotności obiektu docelowego, ale nadal jest bezpieczny, jeśli obiekt docelowy zostanie usunięty.źródło
this
self = shared_from_this()
idiomu, gdy moduł obsługi wywołuje metody z tej samej klasy.shared_ptr : przechowuje prawdziwy obiekt.
poor_ptr : używa
lock
do połączenia z prawdziwym właścicielem lub wshared_ptr
przeciwnym razie zwraca NULL .Z grubsza mówiąc,
weak_ptr
rola jest podobna do roli agencji mieszkaniowej . Bez pośredników, aby wynająć dom, być może będziemy musieli sprawdzić losowe domy w mieście. Agenci upewniają się, że odwiedzamy tylko te domy, które są nadal dostępne i dostępne do wynajęcia.źródło
weak_ptr
dobrze jest również sprawdzić poprawność usunięcia obiektu - szczególnie w testach jednostkowych. Typowy przypadek użycia może wyglądać następująco:źródło
Podczas korzystania ze wskaźników ważne jest, aby zrozumieć różne rodzaje dostępnych wskaźników i kiedy warto je zastosować. Istnieją cztery rodzaje wskaźników w dwóch następujących kategoriach:
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
]
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
]
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
]
Surowe wskaźniki (czasami nazywane „starszymi wskaźnikami” lub „wskaźnikami C”) zapewniają zachowanie wskaźnika „goły kość” i są częstym źródłem błędów i wycieków pamięci. Surowe wskaźniki nie pozwalają na śledzenie własności zasobu, a programiści muszą ręcznie wywołać „usuń”, aby upewnić się, że nie powodują wycieku pamięci. Staje się to trudne, jeśli zasób jest współużytkowany, ponieważ może być trudne sprawdzenie, czy jakieś obiekty nadal wskazują na zasób. Z tych powodów należy zasadniczo unikać surowych wskaźników i stosować je tylko w krytycznych pod względem wydajności sekcjach kodu o ograniczonym zakresie.
Unikalne wskaźniki są podstawowym inteligentnym wskaźnikiem, który „jest właścicielem” podstawowego surowego wskaźnika do zasobu i jest odpowiedzialny za wywoływanie operacji usuwania i zwalnianie przydzielonej pamięci, gdy obiekt „będący właścicielem” niepowtarzalnego wskaźnika wykracza poza zakres. Nazwa „unikatowy” odnosi się do faktu, że tylko jeden obiekt może „posiadać” unikalny wskaźnik w danym momencie. Własność może zostać przeniesiona do innego obiektu za pomocą polecenia przenoszenia, ale unikalny wskaźnik nigdy nie może zostać skopiowany ani udostępniony. Z tych powodów unikalne wskaźniki są dobrą alternatywą dla wskaźników surowych w przypadku, gdy tylko jeden obiekt potrzebuje wskaźnika w danym momencie, a to uwalnia programistę od konieczności zwolnienia pamięci na końcu cyklu życia obiektu będącego właścicielem.
Wskaźniki udostępnione to inny typ inteligentnego wskaźnika, który jest podobny do unikalnych wskaźników, ale pozwala wielu obiektom na posiadanie wskaźnika nad wspólnym wskaźnikiem. Podobnie jak unikalny wskaźnik, współdzielone wskaźniki są odpowiedzialne za zwolnienie przydzielonej pamięci, gdy wszystkie obiekty zostaną skierowane do zasobu. Osiąga to dzięki technice zwanej liczeniem referencji. Za każdym razem, gdy nowy obiekt przejmuje własność wspólnego wskaźnika, liczba referencji jest zwiększana o jeden. Podobnie, gdy obiekt wykracza poza zasięg lub przestaje wskazywać na zasób, liczba odwołań jest zmniejszana o jeden. Gdy liczba odniesień osiągnie zero, przydzielona pamięć zostanie zwolniona. Z tych powodów wspólne wskaźniki są bardzo silnym rodzajem inteligentnego wskaźnika, którego należy używać w dowolnym momencie, gdy wiele obiektów musi wskazywać ten sam zasób.
Wreszcie słabe wskaźniki to inny rodzaj inteligentnego wskaźnika, który zamiast wskazywać bezpośrednio na zasób, wskazuje inny wskaźnik (słaby lub wspólny). Słabe wskaźniki nie mogą uzyskać bezpośredniego dostępu do obiektu, ale mogą stwierdzić, czy obiekt nadal istnieje, czy też wygasł. Słaby wskaźnik można tymczasowo przekonwertować na wskaźnik wspólny, aby uzyskać dostęp do wskazanego obiektu (pod warunkiem, że nadal istnieje). Aby to zilustrować, rozważ następujący przykład:
W tym przykładzie masz słaby wskaźnik na Spotkanie B. Nie jesteś „właścicielem” na Spotkaniu B, więc może zakończyć się bez ciebie i nie wiesz, czy się skończył, chyba że sprawdzisz. Jeśli to się nie skończyło, możesz dołączyć i wziąć udział, w przeciwnym razie nie możesz. Różni się to od posiadania wspólnego wskaźnika do spotkania B, ponieważ byłbyś wtedy „właścicielem” zarówno spotkania A, jak i spotkania B (uczestnicząc w obu jednocześnie).
Przykład ilustruje działanie słabego wskaźnika i jest przydatny, gdy obiekt musi być zewnętrznym obserwatorem , ale nie chce współodpowiedzialności za współwłasność. Jest to szczególnie przydatne w scenariuszu, w którym dwa obiekty muszą wskazywać na siebie nawzajem (inaczej okrągłe odniesienie). Przy współużytkowanych wskaźnikach żaden obiekt nie może zostać zwolniony, ponieważ nadal jest „mocno” wskazywany przez inny obiekt. Gdy jeden ze wskaźników jest słabym wskaźnikiem, obiekt trzymający słaby wskaźnik może nadal uzyskiwać dostęp do drugiego obiektu w razie potrzeby, o ile nadal istnieje.
źródło
Oprócz innych już wspomnianych poprawnych przypadków użycia
std::weak_ptr
jest niesamowite narzędzie w środowisku wielowątkowym, ponieważstd::shared_ptr
w połączeniu zstd::weak_ptr
jest bezpieczny przed zwisającymi wskaźnikami - w przeciwieństwie dostd::unique_ptr
w połączeniu z surowymi wskaźnikamistd::weak_ptr::lock()
jest operacją atomową (zobacz także Informacje o bezpieczeństwie wątków słaby_ptr )Rozważ zadanie załadowania wszystkich obrazów katalogu (~ 10.000) jednocześnie do pamięci (np. Jako pamięć podręczna miniatur). Oczywiście najlepszym sposobem na to jest wątek kontrolny, który obsługuje obrazy i zarządza nimi, oraz wiele wątków roboczych, które ładują obrazy. To jest łatwe zadanie. Oto bardzo uproszczona implementacja (
join()
itp. Jest pomijana, wątki musiałyby być obsługiwane inaczej w rzeczywistej implementacji itp.)Ale staje się to znacznie bardziej skomplikowane, jeśli chcesz przerwać ładowanie obrazów, np. Ponieważ użytkownik wybrał inny katalog. Lub nawet jeśli chcesz zniszczyć menedżera.
Potrzebujesz komunikacji wątków i musisz zatrzymać wszystkie wątki modułu ładującego, zanim będziesz mógł zmienić swoje
m_imageDatas
pole. W przeciwnym razie programy ładujące kontynuowałyby ładowanie, dopóki wszystkie obrazy nie zostaną wykonane - nawet jeśli są już przestarzałe. W uproszczonym przykładzie nie byłoby to zbyt trudne, ale w prawdziwym środowisku rzeczy mogą być znacznie bardziej skomplikowane.Wątki prawdopodobnie byłyby częścią puli wątków używanej przez wielu menedżerów, z których niektóre są zatrzymywane, a niektóre nie są itp. Prostym parametrem
imagesToLoad
byłaby zablokowana kolejka, do której menedżerowie wypychają swoje żądania obrazów z różnych wątków kontrolnych z czytelnikami wstawiającymi żądania - w dowolnej kolejności - na drugim końcu. I tak komunikacja staje się trudna, powolna i podatna na błędy. Bardzo eleganckim sposobem uniknięcia jakiejkolwiek dodatkowej komunikacji w takich przypadkach jest użyciestd::shared_ptr
w połączeniu zstd::weak_ptr
.Ta implementacja jest prawie tak łatwa jak pierwsza, nie wymaga dodatkowej komunikacji wątków i może być częścią puli wątków / kolejki w prawdziwej implementacji. Ponieważ wygasłe obrazy są pomijane, a nieprzeterminowane obrazy są przetwarzane, wątki nigdy nie będą musiały być zatrzymywane podczas normalnej pracy. Zawsze możesz bezpiecznie zmienić ścieżkę lub zniszczyć swoich menedżerów, ponieważ czytnik fn sprawdza, czy wskaźnik własności nie wygasł.
źródło
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: poor_ptr to inteligentny wskaźnik, który przechowuje odwołanie nie będące właścicielem („słaby”) do obiektu zarządzanego przez std :: shared_ptr. Musi zostać przekonwertowany na std :: shared_ptr, aby uzyskać dostęp do obiektu odniesienia.
std :: poor_ptr modeluje tymczasową własność: gdy do obiektu trzeba uzyskać dostęp tylko wtedy, gdy istnieje, i może on zostać w dowolnym momencie usunięty przez kogoś innego, do śledzenia obiektu służy std :: poor_ptr i jest on konwertowany na std: : shared_ptr, aby przejąć tymczasową własność. Jeśli oryginalny std :: shared_ptr zostanie w tym czasie zniszczony, czas życia obiektu zostanie przedłużony do czasu zniszczenia również tymczasowego std :: shared_ptr.
Dodatkowo, std :: poor_ptr służy do przerywania okrągłych odniesień do std :: shared_ptr.
źródło
Wadą współdzielonego wskaźnika jest to, że wskaźnik współdzielony nie może obsłużyć zależności cyklu rodzic-dziecko. Oznacza, że klasa nadrzędna używa obiektu klasy podrzędnej za pomocą wspólnego wskaźnika, w tym samym pliku, jeśli klasa podrzędna korzysta z obiektu klasy nadrzędnej. Współdzielony wskaźnik nie zniszczy wszystkich obiektów, nawet wspólny wskaźnik w ogóle nie wywołuje destruktora w scenariuszu zależności cyklu. zasadniczo współużytkowany wskaźnik nie obsługuje mechanizmu zliczania referencji.
Wadę tę można przezwyciężyć za pomocą słaby_pointer.
źródło
weak_ptr
poradzić sobie z zależnością cykliczną bez zmiany logiki programu jako zamiennikshared_ptr
?” :-)Kiedy nie chcemy posiadać obiektu:
Dawny:
W powyższej klasie wPtr1 nie jest właścicielem zasobu wskazanego przez wPtr1. Jeśli zasób zostanie usunięty, wygasa wPtr1.
Aby uniknąć zależności cyklicznej:
Teraz, jeśli zrobimy shared_ptr klasy B i A, liczba_użytkowania obu wskaźników wynosi dwa.
Gdy parametr shared_ptr zniknie z zakresu, liczba nadal wynosi 1, a zatem obiekt A i B nie zostanie usunięty.
wynik:
Jak widać z danych wyjściowych, wskaźnik A i B nigdy nie są usuwane, a zatem wyciek pamięci.
Aby uniknąć takiego problemu, po prostu użyj słaby_ptr w klasie A zamiast shared_ptr, co ma większy sens.
źródło
Widzę
std::weak_ptr<T>
jako uchwyt dostd::shared_ptr<T>
: Pozwala mi to uzyskać,std::shared_ptr<T>
jeśli nadal istnieje, ale nie wydłuży jego żywotności. Istnieje kilka scenariuszy, w których taki punkt widzenia jest użyteczny:Innym ważnym scenariuszem jest przerywanie cykli w strukturach danych.
Herb Sutter ma doskonałą rozmowę, która wyjaśnia najlepsze wykorzystanie funkcji językowych (w tym przypadku inteligentnych wskaźników), aby zapewnić domyślną swobodę przecieków (co oznacza: wszystko klika w miejscu budowy; trudno go zepsuć). To trzeba obejrzeć.
źródło