wyjaśniono inteligentne wskaźniki (boost)


220

Jaka jest różnica między następującym zestawem wskaźników? Kiedy w ogóle używasz każdego wskaźnika w kodzie produkcyjnym?

Przykłady będą mile widziane!

  1. scoped_ptr

  2. shared_ptr

  3. weak_ptr

  4. intrusive_ptr

Czy używasz wzmocnienia w kodzie produkcyjnym?

Odpowiedzi:


339

Podstawowe właściwości inteligentnych wskaźników

To proste, gdy masz właściwości, które możesz przypisać do każdego inteligentnego wskaźnika. Istnieją trzy ważne właściwości.

  • brak własności
  • przeniesienie własności
  • udział własnościowy

Pierwszy oznacza, że ​​inteligentny wskaźnik nie może usunąć obiektu, ponieważ nie jest on jego właścicielem. Drugi oznacza, że ​​tylko jeden inteligentny wskaźnik może kiedykolwiek wskazywać ten sam obiekt w tym samym czasie. Jeśli inteligentny wskaźnik ma zostać zwrócony z funkcji, własność jest na przykład przenoszona do zwróconego inteligentnego wskaźnika.

Trzeci oznacza, że ​​wiele inteligentnych wskaźników może wskazywać na ten sam obiekt w tym samym czasie. Odnosi się to również do surowego wskaźnika , jednak surowe wskaźniki nie mają ważnej cechy: nie określają, czy są właścicielami, czy nie. Inteligentny wskaźnik udziału własności usunie obiekt, jeśli każdy właściciel zrezygnuje z obiektu. Takie zachowanie jest często potrzebne, więc inteligentne wskaźniki będące własnością wspólną są szeroko rozpowszechnione.

Niektóre posiadające inteligentne wskaźniki nie obsługują ani drugiego, ani trzeciego. Dlatego nie można ich zwrócić z funkcji ani przekazać gdzie indziej. Który jest najbardziej odpowiedni do RAIIcelów, w których inteligentny wskaźnik jest przechowywany lokalnie i jest właśnie tworzony, aby uwalniał obiekt po przekroczeniu zakresu.

Udział własności można zrealizować, mając konstruktor kopii. To oczywiście kopiuje inteligentny wskaźnik, a zarówno kopia, jak i oryginał będą odnosić się do tego samego obiektu. Przeniesienia własności nie można obecnie tak naprawdę zrealizować w C ++, ponieważ nie ma możliwości przeniesienia czegoś z jednego obiektu do drugiego obsługiwanego przez język: Jeśli spróbujesz zwrócić obiekt z funkcji, dzieje się tak, że obiekt jest kopiowany. Tak więc inteligentny wskaźnik, który realizuje przeniesienie własności, musi użyć konstruktora kopiowania, aby zrealizować to przeniesienie własności. To z kolei przerywa jego użycie w kontenerach, ponieważ wymagania określają pewne zachowanie konstruktora kopiującego elementów kontenerów, które jest niezgodne z tym tak zwanym zachowaniem „inteligentnego konstruktora” tych inteligentnych wskaźników.

C ++ 1x zapewnia natywne wsparcie dla przenoszenia własności, wprowadzając tak zwane „konstruktory ruchów” i „operatory przypisania ruchów”. Jest również wyposażony w taki inteligentny wskaźnik przeniesienia własności o nazwie unique_ptr.

Kategoryzowanie inteligentnych wskaźników

scoped_ptrto inteligentny wskaźnik, którego nie można przenieść ani udostępnić. Jest to użyteczne, jeśli lokalnie potrzebujesz przydzielić pamięć, ale upewnij się, że zostanie ponownie zwolniona, gdy wyjdzie poza zakres. Ale nadal możesz go wymienić na inny scoped_ptr, jeśli chcesz.

shared_ptrto inteligentny wskaźnik, który dzieli własność (trzeci rodzaj powyżej). Jest liczony jako odniesienie, dzięki czemu może zobaczyć, kiedy ostatnia jego kopia wykracza poza zakres, a następnie uwalnia zarządzany obiekt.

weak_ptrjest nie posiadającym inteligentnego wskaźnika. Służy do odwoływania się do zarządzanego obiektu (zarządzanego przez shared_ptr) bez dodawania liczby referencji. Zwykle trzeba wyciągnąć surowy wskaźnik z shared_ptr i skopiować go. Nie byłoby to jednak bezpieczne, ponieważ nie można sprawdzić, kiedy obiekt został faktycznie usunięty. Zatem słaby_ptr zapewnia środki poprzez odwołanie się do obiektu zarządzanego przez shared_ptr. Jeśli potrzebujesz uzyskać dostęp do obiektu, możesz zablokować zarządzanie nim (aby uniknąć tego w innym wątku, shared_ptr uwalnia go podczas korzystania z obiektu), a następnie użyj go. Jeśli słaby_ptr wskazuje na obiekt już usunięty, zauważy cię, zgłaszając wyjątek. Korzystanie ze słaby_ptr jest najbardziej korzystne, gdy masz cykliczne odwołanie: liczenie odniesień nie może łatwo poradzić sobie z taką sytuacją.

intrusive_ptrjest podobny do shared_ptr, ale nie zachowuje liczby referencji w shared_ptr, ale pozostawia zwiększenie / zmniejszenie liczby do niektórych funkcji pomocniczych, które muszą być zdefiniowane przez zarządzany obiekt. Ma to tę zaletę, że już przywoływany obiekt (który ma liczbę referencji zwiększoną przez zewnętrzny mechanizm liczenia referencji) może być upchnięty w intrusive_ptr - ponieważ liczba referencji nie jest już wewnętrzna względem inteligentnego wskaźnika, ale inteligentny wskaźnik wykorzystuje istniejący referencyjny mechanizm zliczania.

unique_ptrjest wskaźnikiem przeniesienia własności. Nie możesz go skopiować, ale możesz go przenieść za pomocą konstruktorów ruchów C ++ 1x:

unique_ptr<type> p(new type);
unique_ptr<type> q(p); // not legal!
unique_ptr<type> r(move(p)); // legal. p is now empty, but r owns the object
unique_ptr<type> s(function_returning_a_unique_ptr()); // legal!

Jest to semantyka, której przestrzega std :: auto_ptr, ale z powodu braku natywnej obsługi ruchu, nie zapewnia ich bez pułapek. Unique_ptr automatycznie wykradnie zasoby z tymczasowego innego pliku Unique_ptr, który jest jedną z kluczowych cech semantyki ruchu. auto_ptr zostanie wycofane w następnej wersji C ++ Standard na korzyść Unique_ptr. C ++ 1x zezwoli również na upychanie obiektów, które są ruchome, ale nie można ich kopiować do kontenerów. Możesz na przykład włożyć unikatowe elementy do wektora. Zatrzymam się tutaj i odsyłam do świetnego artykułu na ten temat, jeśli chcesz przeczytać więcej na ten temat.


3
dzięki za pochwałę. Doceniam to, więc teraz dostaniesz +1: p
Johannes Schaub - litb

@litb: Mam wątpliwości co do „przeniesienia własności”; Zgadzam się, że nie ma rzeczywistego przeniesienia własności między obiektami w C ++ 03, ale w przypadku inteligentnych wskaźników nie można tego zrobić za pomocą mechanizmu niszczącego kopiowania opisanego tutaj informit.com/articles/article.aspx?p=31529&seqNum= 5 .
legends2k

3
fantastyczna odpowiedź. Uwaga: auto_ptrjest już przestarzałe (C ++ 11).
nickolay

2
„to z kolei przerywa jego użycie w kontenerach, ponieważ wymagania określają pewne zachowanie konstruktora kopiującego elementów kontenerów, które jest niezgodne z tym tak zwanym zachowaniem„ inteligentnego konstruktora ”tych inteligentnych wskaźników.” Nie dostałem tej części.
Raja

Powiedziano mi również, że intrusive_ptrmoże to być lepsze niż shared_ptrdla lepszej spójności pamięci podręcznej. Najwyraźniej pamięć podręczna działa lepiej, jeśli przechowujesz liczbę referencji jako część pamięci samego zarządzanego obiektu zamiast osobnego obiektu. Można to zaimplementować w szablonie lub nadklasie zarządzanego obiektu.
Eliot

91

scoped_ptr jest najprostszy. Kiedy wychodzi poza zakres, zostaje zniszczony. Poniższy kod jest nielegalny (scoped_ptrs nie można kopiować), ale zilustruje pewną kwestię:

std::vector< scoped_ptr<T> > tPtrVec;
{
     scoped_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // raw T* is freed
}
tPtrVec[0]->DoSomething(); // accessing freed memory

shared_ptr jest liczony jako referencja. Za każdym razem, gdy nastąpi kopia lub przypisanie, liczba referencji jest zwiększana. Za każdym razem, gdy uruchamiany jest destruktor instancji, liczba referencyjna surowego T * jest zmniejszana. Gdy wynosi 0, wskaźnik jest zwalniany.

std::vector< shared_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     // This copy to tPtrVec.push_back and ultimately to the vector storage
     // causes the reference count to go from 1->2
     tPtrVec.push_back(tPtr);
     // num references to T goes from 2->1 on the destruction of tPtr
}
tPtrVec[0]->DoSomething(); // raw T* still exists, so this is safe

słaby_ptr jest słabym odniesieniem do współdzielonego wskaźnika, który wymaga sprawdzenia, czy wskazany na współdzielony_ptr nadal istnieje

std::vector< weak_ptr<T> > tPtrVec;
{
     shared_ptr<T> tPtr(new T());
     tPtrVec.push_back(tPtr);
     // num references to T goes from 1->0
}
shared_ptr<T> tPtrAccessed =  tPtrVec[0].lock();
if (tPtrAccessed[0].get() == 0)
{
     cout << "Raw T* was freed, can't access it"
}
else
{
     tPtrVec[0]->DoSomething(); // raw 
}

intrusive_ptr jest zwykle używany, gdy istnieje ptr inteligentnego ptr innej firmy, którego musisz użyć. Wywoła funkcję bezpłatną w celu dodania i zmniejszenia liczby referencji. Aby uzyskać więcej informacji, zobacz link do ulepszenia dokumentacji.


nie if (tPtrAccessed[0].get() == 0)ma tak być if (tPtrAccessed.get() == 0) ?
Rajeshwar,

@DougT. Czy uważasz, że Java korzysta z tego samego pomysłu w odniesieniu do referencji? Miękki, twardy, słaby itp?
gansub

20

Nie pomijaj boost::ptr_containerw żadnej ankiecie dotyczącej inteligentnych wskaźników. Mogą być bezcenne w sytuacjach, w których np. std::vector<boost::shared_ptr<T> >Byłoby zbyt wolne.


Właściwie, kiedy ostatnio próbowałem, testy porównawcze wykazały, że różnica w wydajności znacznie się zmniejszyła, odkąd napisałem to, przynajmniej na typowym PC! Bardziej wydajne podejście ptr_container może jednak mieć pewne zalety w niszowych przypadkach użycia.
dzisiaj

12

Popieram radę dotyczącą przeglądania dokumentacji. To nie jest tak przerażające, jak się wydaje. I kilka krótkich wskazówek:

  • scoped_ptr- wskaźnik automatycznie usuwany, gdy wykracza poza zakres. Uwaga - przypisanie nie jest możliwe, ale nie nakłada kosztów ogólnych
  • intrusive_ptr- wskaźnik zliczania odniesienia bez narzutu smart_ptr. Jednak sam obiekt przechowuje liczbę referencji
  • weak_ptr- współpracuje z shared_ptrsytuacjami powodującymi zależności cykliczne (przeczytaj dokumentację i wyszukaj w Google fajny obraz;)
  • shared_ptr - ogólny, najmocniejszy (i najcięższy) spośród inteligentnych wskaźników (od tych oferowanych przez boost)
  • Jest też stary auto_ptr, który zapewnia, że ​​obiekt, na który wskazuje, jest automatycznie niszczony, gdy kontrola opuszcza zakres. Ma jednak inną semantykę kopiowania niż reszta facetów.
  • unique_ptr- przyjdzie z C ++ 0x

Odpowiedź na edycję: Tak


8
Przybyłem tutaj, ponieważ uważałem, że dokumentacja doładowania jest zbyt przerażająca.
Francois Botha
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.