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.
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ąc std::shared_ptr
zarządzać danymi i dostarczając std::weak_ptr
użytkownikom danych, użytkownicy mogą sprawdzić ważność danych, dzwoniąc expired()
lub lock()
.
Nie można tego zrobić std::shared_ptr
samemu, ponieważ wszystkie std::shared_ptr
instancje dzielą własność danych, które nie są usuwane przed usunięciem wszystkich instancji std::shared_ptr
. Oto przykład sprawdzania zwisającego wskaźnika za pomocą lock()
:
#include <iostream>
#include <memory>
int main()
{
// OLD, problem with dangling pointer
// PROBLEM: ref will point to undefined data!
int* ptr = new int(10);
int* ref = ptr;
delete ptr;
// NEW
// SOLUTION: check expired() or lock() to determine if pointer is valid
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
// deletes managed object, acquires new pointer
sptr.reset(new int);
*sptr = 5;
// get pointer to new data without taking ownership
std::weak_ptr<int> weak2 = sptr;
// weak1 is expired!
if(auto tmp = weak1.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak1 is expired\n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak2 is expired\n";
}
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
i Member
przedmioty.
Oczywiście jest to związek: Team
obiekt będzie miał do niego wskaźniki Members
. I prawdopodobne jest, że członkowie będą mieli także wskaźnik cofania do swojego Team
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żywa weak_ptr
do swojego rodzica i konwertuje go tymczasowo na, shared_ptr
kiedy potrzebuje dostępu do swojego rodzica.
Przechowuj słaby ptr:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
w razie potrzeby użyj go
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
// yes, it may fail if the parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
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żywania shared_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 a std::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.
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.
void MyClass::startTimer()
{
std::weak_ptr<MyClass> weak = shared_from_this();
timer_.async_wait( [weak](const boost::system::error_code& ec)
{
auto self = weak.lock();
if (self)
{
self->handleTimeout();
}
else
{
std::cout << "Target object no longer exists!\n";
}
} );
}
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.
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 w shared_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.
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:
std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
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.
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 z std::weak_ptr
jest bezpieczny przed zwisającymi wskaźnikami - w przeciwieństwie do std::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.)
// a simplified class to hold the thumbnail and data
struct ImageData {
std::string path;
std::unique_ptr<YourFavoriteImageLibData> image;
};
// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
for( auto& imageData : imagesToLoad )
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas =
splitImageDatas( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
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życie std::shared_ptr
w połączeniu z std::weak_ptr
.
// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
for( auto& imageDataWeak : imagesToLoad ) {
std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
if( !imageData )
continue;
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas =
splitImageDatasToWeak( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
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ł.
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.
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.
weak_ptr
poradzić sobie z zależnością cykliczną bez zmiany logiki programu jako zamiennik shared_ptr
?” :-)
Kiedy nie chcemy posiadać obiektu:
Dawny:
class A
{
shared_ptr<int> sPtr1;
weak_ptr<int> wPtr1;
}
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:
shard_ptr<A> <----| shared_ptr<B> <------
^ | ^ |
| | | |
| | | |
| | | |
| | | |
class A | class B |
| | | |
| ------------ |
| |
-------------------------------------
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.
class B;
class A
{
shared_ptr<B> sP1; // use weak_ptr instead to avoid CD
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void setShared(shared_ptr<B>& p)
{
sP1 = p;
}
};
class B
{
shared_ptr<A> sP1;
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void setShared(shared_ptr<A>& p)
{
sP1 = p;
}
};
int main()
{
shared_ptr<A> aPtr(new A);
shared_ptr<B> bPtr(new B);
aPtr->setShared(bPtr);
bPtr->setShared(aPtr);
return 0;
}
wynik:
A()
B()
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.
Widzę std::weak_ptr<T>
jako uchwyt do std::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:
// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;
// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.
struct Widget {
std::weak_ptr< Texture > texture_handle;
void render() {
if (auto texture = texture_handle.get(); texture) {
// do stuff with texture. Warning: `texture`
// is now extending the lifetime because it
// is a std::shared_ptr< Texture >.
} else {
// gracefully degrade; there's no texture.
}
}
};
Innym ważnym scenariuszem jest przerywanie cykli w strukturach danych.
// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > next;
std::shared_ptr< Node > prev;
};
// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::shared_ptr< Node > next;
std::weak_ptr< Node > prev;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::weak_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
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ć.