Kiedy przydatne jest std :: poor_ptr?


Odpowiedzi:


231

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.


8
Więc std :: wake_ptr może wskazywać tylko tam, gdzie wskazuje inny wskaźnik i wskazuje na nullptr, gdy wskazany obiekt jest usuwany / nie jest już wskazywany przez inne wskaźniki?

27
@RM: Zasadniczo tak. Kiedy masz słaby wskaźnik, możesz spróbować wypromować go do silnego wskaźnika. Jeśli ten obiekt nadal istnieje (ponieważ przynajmniej jeden silny wskaźnik do niego istnieje), operacja zakończy się powodzeniem i da ci silny wskaźnik do niego. Jeśli ten obiekt nie istnieje (ponieważ wszystkie silne wskaźniki zniknęły), wówczas operacja kończy się niepowodzeniem (i zazwyczaj reagujesz, odrzucając słaby wskaźnik).
David Schwartz,

12
Podczas gdy silny wskaźnik utrzymuje obiekt przy życiu, słaby_ptr może na niego patrzeć ... bez marnowania czasu życia obiektu.
Vivandiere,

3
Innym przykładem, z którego przynajmniej kilka razy korzystałem, jest implementacja obserwatorów, czasem wygodne jest, aby podmiot utrzymywał listę słabych wskaźników i przeprowadzał własne czyszczenie listy. Oszczędza to trochę wysiłku jawnego usuwania obserwatorów po ich usunięciu, a co ważniejsze, nie musisz mieć informacji o tematach podczas niszczenia obserwatorów, co ogólnie bardzo upraszcza.
Jason C

3
Zaraz, co jest nie tak z pamięcią podręczną przechowującą plik shared_ptr i po prostu usuwającą go z listy, kiedy należy go usunąć z pamięci? Każdy użytkownik będzie utrzymywał współużytkowany plik_ptr tak samo, a buforowany zasób zostanie wyczyszczony, gdy tylko wszyscy użytkownicy skończą z nim.
rubenvb,

299

std::weak_ptrto 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_ptrzarządzać danymi i dostarczając std::weak_ptrużytkownikom danych, użytkownicy mogą sprawdzić ważność danych, dzwoniąc expired()lub lock().

Nie można tego zrobić std::shared_ptrsamemu, ponieważ wszystkie std::shared_ptrinstancje 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";
}

1
Ok, to tak, jakbyś lokalnie ustawił wskaźnik (będący właścicielem) na null (usuń pamięć), wszystkie inne (słabe) wskaźniki tej samej pamięci również są ustawione na null
Pat-Laugh

std::weak_ptr::locktworzy nowy, std::shared_ptrktóry dzieli własność zarządzanego obiektu.
Sahib Yar

129

Kolejna odpowiedź, miejmy nadzieję, prostsza. (dla innych pracowników Google)

Załóżmy, że masz Teami Memberprzedmioty.

Oczywiście jest to związek: Teamobiekt będzie miał do niego wskaźniki Members. I prawdopodobne jest, że członkowie będą mieli także wskaźnik cofania do swojego Teamobiektu.

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_ptra „własność” używa weak_ptrdo swojego rodzica i konwertuje go tymczasowo na, shared_ptrkiedy 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

1
Jak to jest wyciek pamięci? Jeśli drużyna zostanie zniszczona, zniszczy swoich członków, więc liczba referencji shared_ptr wyniesie 0, a także zniszczona?
paulm

4
Zespół @paulm nie zniszczy „swoich” członków. Chodzi o shared_ptrto, 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_ptrznaku „na początek”.
Offirmo,

1
Nie zniszczy ich, ale jego shared_ptr nie będzie się z tym wiązać, zmniejsza wartość use_count, więc w tym momencie use_count wynosi 0, więc shared_ptr usunie to, na co wskazuje?
paulm

2
@paulm Masz rację. Ale skoro w tym przykładzie zespół jest również shared_ptrokreślany przez „członków zespołu”, kiedy zostanie zniszczony? Opisujesz przypadek, w którym nie ma pętli.
Offirmo,

14
Myślę, że nie jest tak źle. Jeśli członek może należeć do wielu zespołów, użycie referencji nie będzie działać.
Mazyod

22

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.


4
: Brzmi jak dobry przykład, ale czy mógłbyś bardziej rozwinąć swój przykład? Myślę, że kiedy zadanie jest zakończone, powinno być już usunięte ze std :: vector <std :: poor_ptr <Task>> bez okresowej kontroli. Więc nie jestem pewien, czy std :: vector <std :: poor_ptr <>> jest tutaj bardzo pomocne.
Gob00st

Podobny komentarz z kolejkami: powiedzmy, że masz obiekty i ustawisz je w kolejce do jakiegoś zasobu, obiekty można usunąć podczas oczekiwania. Jeśli więc ustawisz w kolejce słaby_ptrs, nie musisz zawracać sobie głowy usuwaniem wpisów z tej kolejki. Weak_ptrs zostaną unieważnione, a następnie odrzucone, gdy zostaną odkryte.
zzz777

1
@ zzz777: Logika, która unieważnia obiekty, może nawet nie być świadoma istnienia kolejki lub wektora obserwatorów. Tak więc obserwator wykonuje osobną pętlę nad słabymi wskaźnikami, działając na te, które jeszcze żyją i usuwając martwe z pojemnika ...
Kerrek SB

1
@KerekSB: tak, aw przypadku kolejki nie musisz nawet przechodzić do osobnej pętli - wtedy dostępny jest zasób, odrzuć wygasłe słaby_ptrs (jeśli istnieje), dopóki nie uzyskasz poprawnego (jeśli istnieje).
zzz777

Możesz także poprosić o usunięcie wątków z kolekcji, ale spowodowałoby to zależność i wymagałoby zablokowania.
ciekawy,

16

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_ptrobiektu asynchonicznego obiektu obsługi za pomocą std::bindprzechwytywania 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.


Dlaczego tak długo zajęło znalezienie tej odpowiedzi ... PS, której nie używasz do przechwytywaniathis
Orwellophile

@Orwellophile naprawiony. Siła nawyku przy użyciu self = shared_from_this()idiomu, gdy moduł obsługi wywołuje metody z tej samej klasy.
Emile Cormier,

16

shared_ptr : przechowuje prawdziwy obiekt.

poor_ptr : używa lockdo połączenia z prawdziwym właścicielem lub w shared_ptrprzeciwnym razie zwraca NULL .

słaby ptr

Z grubsza mówiąc, weak_ptrrola 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.


14

weak_ptrdobrze 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());

13

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:

  • Surowe wskaźniki:
    • Wskaźnik surowy [tj. SomeClass* ptrToSomeClass = new SomeClass();]
  • Inteligentne wskaźniki:
    • Unikalne wskaźniki [tj.
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • Wspólne wskaźniki [tj.
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • Słabe wskaźniki [tj.
      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:

  • Jesteś zajęty i masz nakładające się spotkania: Spotkanie A i Spotkanie B.
  • Decydujesz się na spotkanie A, a Twój współpracownik idzie na spotkanie B.
  • Mówisz swojemu współpracownikowi, że jeśli Spotkanie B będzie kontynuowane po zakończeniu Spotkania A, dołączysz
  • Można odtworzyć następujące dwa scenariusze:
    • Spotkanie A kończy się, a spotkanie B nadal trwa, więc dołączasz
    • Spotkanie A kończy się, a spotkanie B również się zakończyło, więc nie możesz dołączyć

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.


6

Oprócz innych już wspomnianych poprawnych przypadków użycia std::weak_ptrjest niesamowite narzędzie w środowisku wielowątkowym, ponieważ

  • Nie jest właścicielem obiektu, więc nie może utrudnić usunięcia w innym wątku
  • std::shared_ptrw połączeniu z std::weak_ptrjest bezpieczny przed zwisającymi wskaźnikami - w przeciwieństwie do std::unique_ptrw połączeniu z surowymi wskaźnikami
  • std::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_imageDataspole. 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 imagesToLoadbył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_ptrw 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ł.


2

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.


jak przełamać okólniki ” jak?
curiousguy

2

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.


Jak słaba referencja może poradzić sobie z zależnością cykliczną?
curiousguy

1
@curiousguy, dziecko stosuje słabe odniesienie do rodzica, a następnie rodzica można zwolnić, gdy nie ma wspólnych (silnych) odniesień do niego. Dlatego podczas uzyskiwania dostępu do rodzica za pośrednictwem dziecka słabe odniesienie należy przetestować, aby sprawdzić, czy rodzic jest nadal dostępny. Alternatywnie, aby uniknąć tego dodatkowego warunku, mechanizm śledzenia referencji cyklicznych (albo przeglądanie znaczników lub sondowanie dekretu rachunków, z których oba mają słabą wydajność asymptotyczną) może przerwać wspólne referencje cykliczne, gdy jedyne wspólne referencje do rodzica i dziecka pochodzą z każdego z nich inny.
Shelby Moore III

@ShelbyMooreIII ”musiał przetestować, aby zobaczyć, czy rodzic jest nadal dostępny ” tak, i musisz być w stanie poprawnie reagować na niedostępną sprawę! Co nie występuje w przypadku rzeczywistego (tj. Silnego) ref. Co oznacza, że ​​słaby ref nie jest spadkiem wymiany: wymaga zmiany logiki.
ciekawy,

2
@curiousguy nie zapytałeś: „Jak weak_ptrporadzić sobie z zależnością cykliczną bez zmiany logiki programu jako zamiennik shared_ptr?” :-)
Shelby Moore III

2

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.


2

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ć.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.