Co to jest inteligentny wskaźnik i kiedy powinienem go użyć?
Co to jest inteligentny wskaźnik i kiedy powinienem go użyć?
Odpowiedzi:
AKTUALIZACJA
Ta odpowiedź jest raczej stara i opisuje to, co było wtedy „dobre”, czyli inteligentne wskaźniki dostarczone przez bibliotekę Boost. Od wersji C ++ 11 standardowa biblioteka zapewnia wystarczającą liczbę typów inteligentnych wskaźników, dlatego powinieneś preferować użycie std::unique_ptr
, std::shared_ptr
i std::weak_ptr
.
Było też std::auto_ptr
. Był bardzo podobny do wskaźnika celowniczego, tyle że miał także „specjalną” niebezpieczną zdolność do kopiowania - która również nieoczekiwanie przenosi własność.
Został przestarzały w C ++ 11 i usunięty w C ++ 17 , więc nie powinieneś go używać.
std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership.
// p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.
STARA ODPOWIEDŹ
Inteligentny wskaźnik to klasa, która otacza „surowy” (lub „goły”) wskaźnik C ++, aby zarządzać żywotnością wskazywanego obiektu. Nie ma jednego inteligentnego typu wskaźnika, ale wszystkie próbują wyodrębnić surowy wskaźnik w praktyczny sposób.
Inteligentne wskaźniki powinny być preferowane nad surowymi wskaźnikami. Jeśli uważasz, że musisz użyć wskaźników (najpierw zastanów się, czy naprawdę tak robisz), zwykle powinieneś użyć inteligentnego wskaźnika, ponieważ może to złagodzić wiele problemów z surowymi wskaźnikami, głównie zapominając o usunięciu obiektu i wycieku pamięci.
Dzięki surowym wskaźnikom programista musi jawnie zniszczyć obiekt, gdy nie jest on już użyteczny.
// Need to create the object to achieve some goal
MyObject* ptr = new MyObject();
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?
Inteligentny wskaźnik przez porównanie definiuje zasadę, kiedy obiekt zostanie zniszczony. Nadal musisz stworzyć obiekt, ale nie musisz się już martwić o jego zniszczenie.
SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.
// Destruction of the object happens, depending
// on the policy the smart pointer class uses.
// Destruction would happen even if DoSomething()
// raises an exception
Najprostsza w użyciu zasada obejmuje zakres obiektu opakowania inteligentnego wskaźnika, na przykład zaimplementowanego przez boost::scoped_ptr
lub std::unique_ptr
.
void f()
{
{
std::unique_ptr<MyObject> ptr(new MyObject());
ptr->DoSomethingUseful();
} // ptr goes out of scope --
// the MyObject is automatically destroyed.
// ptr->Oops(); // Compile error: "ptr" not defined
// since it is no longer in scope.
}
Pamiętaj, że std::unique_ptr
nie można skopiować instancji. Zapobiega to wielokrotnemu usuwaniu wskaźnika (niepoprawnie). Możesz jednak przekazywać odniesienia do innych wywoływanych funkcji.
std::unique_ptr
s są przydatne, gdy chcesz powiązać czas życia obiektu z określonym blokiem kodu lub jeśli osadziłeś go jako dane członka w innym obiekcie, czas życia tego innego obiektu. Obiekt istnieje do momentu opuszczenia zawierającego go bloku kodu lub do momentu zniszczenia zawierającego go obiektu.
Bardziej złożone zasady dotyczące inteligentnych wskaźników obejmują liczenie referencji przez wskaźnik. Pozwala to na skopiowanie wskaźnika. Gdy ostatnie „odniesienie” do obiektu zostanie zniszczone, obiekt zostanie usunięty. Ta polityka jest wdrażana przez boost::shared_ptr
i std::shared_ptr
.
void f()
{
typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
MyObjectPtr p1; // Empty
{
MyObjectPtr p2(new MyObject());
// There is now one "reference" to the created object
p1 = p2; // Copy the pointer.
// There are now two references to the object.
} // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero.
// The object is deleted.
Wskaźniki liczone w odniesieniach są bardzo przydatne, gdy czas życia obiektu jest znacznie bardziej skomplikowany i nie jest bezpośrednio powiązany z określoną sekcją kodu lub innym obiektem.
Istnieje jedna wada w stosunku do liczonych wskaźników - możliwość utworzenia wiszącego odniesienia:
// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!
Inną możliwością jest tworzenie referencji cyklicznych:
struct Owner {
std::shared_ptr<Owner> other;
};
std::shared_ptr<Owner> p1 (new Owner());
std::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1
// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!
Aby obejść ten problem, zarówno Boost, jak i C ++ 11 zdefiniowały a, weak_ptr
aby zdefiniować słabe (niepoliczone) odwołanie do a shared_ptr
.
std::auto_ptr<MyObject> p1 (new MyObject());
zamiast std::auto_ptr<MyObject> p1 (new Owner());
?
const std::auto_ptr
jest bezpieczny w użyciu, jeśli utkniesz w C ++ 03. Używałem go dość często do wzorca pimpl, dopóki nie uzyskałem dostępu do C ++ 11.
Oto prosta odpowiedź na współczesne C ++ (C ++ 11 i nowsze):
std::unique_ptr
gdy nie zamierzasz przechowywać wielu odwołań do tego samego obiektu. Na przykład użyj go jako wskaźnika do pamięci, który zostaje przydzielony po wejściu w pewien zakres i zwolniony po wyjściu z zakresu.std::shared_ptr
gdy chcesz odwoływać się do swojego obiektu z wielu miejsc - i nie chcesz, aby Twój obiekt został cofnięty, dopóki wszystkie te odniesienia nie znikną.std::weak_ptr
jeśli chcesz odwoływać się do swojego obiektu z wielu miejsc - w przypadku tych odniesień, dla których można zignorować i cofnąć przydział (aby zauważyć, że obiekt zniknął, gdy próbujesz wyrejestrować).boost::
inteligentnych wskaźników lub std::auto_ptr
wyjątkowych przypadków, o których możesz przeczytać, jeśli musisz.T*
jest do std::unique_ptr<T>
tego, co std::weak_ptr<T>
jeststd::shared_ptr<T>
Inteligentny wskaźnik jest podobny do wskaźnika z pewnymi dodatkowymi funkcjami, np. Automatycznym zwolnieniem pamięci, zliczaniem referencji itp.
Małe wprowadzenie jest dostępne na stronie Inteligentne wskaźniki - Co, dlaczego, co? .
Jednym z prostych typów inteligentnych wskaźników jest std::auto_ptr
(rozdział 20.4.5 standardu C ++), który pozwala automatycznie zwolnić pamięć, gdy jest poza zakresem, i który jest bardziej niezawodny niż proste użycie wskaźnika, gdy są zgłaszane wyjątki, chociaż mniej elastyczne.
Innym wygodnym typem jest boost::shared_ptr
implementacja zliczania referencji i automatyczne zwalnianie pamięci, gdy nie ma żadnych referencji do obiektu. Pomaga to uniknąć wycieków pamięci i jest łatwy w użyciu do implementacji RAII .
Temat jest szczegółowo opisany w książce „C ++ Templates: The Complete Guide” autorstwa Davida Vandevoorde, Nicolai M. Josuttis , rozdział 20. Inteligentne wskaźniki. Niektóre tematy obejmowały:
std::auto_ptr
jest przestarzałe i bardzo odradza, ponieważ możesz przypadkowo przenieść własność. - C ++ 11 eliminuje potrzebę stosowania Boost,: std::unique_ptr
, std::shared_ptr
istd::weak_ptr
Definicje podane przez Chrisa, Sergdeva i Llyoda są poprawne. Wolę jednak prostszą definicję, aby moje życie było proste: inteligentny wskaźnik to po prostu klasa, która przeciąża operatory ->
i *
. Co oznacza, że twój obiekt semantycznie wygląda jak wskaźnik, ale możesz sprawić, że zrobi to znacznie fajniejsze rzeczy, w tym zliczanie referencji, automatyczne niszczenie itp.
shared_ptr
I auto_ptr
są wystarczające w większości przypadków, ale mają własny zestaw małych osobliwości.
Inteligentny wskaźnik jest podobny do zwykłego (pisanego na maszynie) wskaźnika, takiego jak „char *”, z wyjątkiem sytuacji, gdy sam wskaźnik wykracza poza zasięg, wówczas to, na co wskazuje, jest również usuwane. Możesz go używać tak, jak zwykłego wskaźnika, używając „->”, ale nie, jeśli potrzebujesz rzeczywistego wskaźnika do danych. W tym celu możesz użyć „& * ptr”.
Przydaje się do:
Obiekty, które należy przypisać do nowego, ale które mają mieć taki sam okres istnienia jak coś na tym stosie. Jeśli obiekt jest przypisany do inteligentnego wskaźnika, zostaną one usunięte, gdy program opuści tę funkcję / blok.
Elementy danych klas, dzięki czemu po usunięciu obiektu usuwane są również wszystkie posiadane dane, bez specjalnego kodu w destruktorze (musisz upewnić się, że destruktor jest wirtualny, co prawie zawsze dobrze jest zrobić) .
Możesz nie chcieć używać inteligentnego wskaźnika, gdy:
Zobacz też:
Większość rodzajów inteligentnych wskaźników radzi sobie z usuwaniem wskaźnika do obiektu. Jest to bardzo przydatne, ponieważ nie musisz już myśleć o ręcznym usuwaniu obiektów.
Najczęściej używane inteligentne wskaźniki to std::tr1::shared_ptr
(lub boost::shared_ptr
), a rzadziej std::auto_ptr
. Polecam regularne stosowanie shared_ptr
.
shared_ptr
jest bardzo wszechstronny i zajmuje się wieloma różnymi scenariuszami usuwania, w tym przypadkami, w których obiekty muszą być „przekazywane przez granice bibliotek DLL” (częsty przypadek koszmaru, jeśli różne libc
kody są używane między kodem a bibliotekami DLL).
Inteligentny wskaźnik to obiekt, który działa jak wskaźnik, ale dodatkowo zapewnia kontrolę nad budową, zniszczeniem, kopiowaniem, przenoszeniem i dereferencją.
Można wdrożyć własny inteligentny wskaźnik, ale wiele bibliotek zapewnia również inteligentne implementacje wskaźników, z których każda ma inne zalety i wady.
Na przykład Boost zapewnia następujące implementacje inteligentnych wskaźników:
shared_ptr<T>
jest wskaźnikiem T
użycia licznika referencji w celu ustalenia, kiedy obiekt nie jest już potrzebny.scoped_ptr<T>
jest wskaźnikiem automatycznie usuwanym, gdy wykracza poza zakres. Zadanie nie jest możliwe.intrusive_ptr<T>
jest kolejnym wskaźnikiem liczącym odniesienia. Zapewnia lepszą wydajność niż shared_ptr
, ale wymaga, aby ten typ T
miał własny mechanizm zliczania referencji.weak_ptr<T>
jest słabym wskaźnikiem, działającym w połączeniu z shared_ptr
unikaniem okrągłych odniesień.shared_array<T>
jest jak shared_ptr
, ale dla tablic z T
.scoped_array<T>
jest jak scoped_ptr
, ale dla tablic z T
.Są to tylko jeden liniowy opis każdego z nich i mogą być używane według potrzeb, w celu uzyskania dalszych szczegółów i przykładów można zapoznać się z dokumentacją wzmocnienia.
Dodatkowo standardowa biblioteka C ++ zawiera trzy inteligentne wskaźniki; std::unique_ptr
dla unikalnej własności, std::shared_ptr
dla wspólnej własności i std::weak_ptr
. std::auto_ptr
istniał w C ++ 03, ale teraz jest przestarzały.
scoped_ptr
nie jest to deklaracja lokalna const unique_ptr
- która również jest usuwana po wyjściu z zakresu.
Oto link do podobnych odpowiedzi: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html
Inteligentny wskaźnik to obiekt, który działa, wygląda i działa jak zwykły wskaźnik, ale oferuje większą funkcjonalność. W C ++ inteligentne wskaźniki są implementowane jako klasy szablonów, które zawierają w sobie wskaźnik i zastępują standardowe operatory wskaźnika. Mają wiele zalet w porównaniu ze zwykłymi wskaźnikami. Gwarantuje się, że zostaną zainicjowane jako wskaźniki zerowe lub wskaźniki do obiektu stosu. Sprawdzane jest przekierowanie przez wskaźnik zerowy. Nigdy nie jest konieczne usuwanie. Obiekty są automatycznie zwalniane, gdy ostatni wskaźnik do nich zniknie. Jednym ze znaczących problemów z tymi inteligentnymi wskaźnikami jest to, że w przeciwieństwie do zwykłych wskaźników, nie szanują dziedziczenia. Inteligentne wskaźniki nie są atrakcyjne dla kodu polimorficznego. Poniżej podano przykład implementacji inteligentnych wskaźników.
Przykład:
template <class X>
class smart_pointer
{
public:
smart_pointer(); // makes a null pointer
smart_pointer(const X& x) // makes pointer to copy of x
X& operator *( );
const X& operator*( ) const;
X* operator->() const;
smart_pointer(const smart_pointer <X> &);
const smart_pointer <X> & operator =(const smart_pointer<X>&);
~smart_pointer();
private:
//...
};
Ta klasa implementuje inteligentny wskaźnik do obiektu typu X. Sam obiekt znajduje się na stercie. Oto jak z niego korzystać:
smart_pointer <employee> p= employee("Harris",1333);
Podobnie jak inne przeciążone operatory, p będzie zachowywać się jak zwykły wskaźnik,
cout<<*p;
p->raise_salary(0.5);
http://en.wikipedia.org/wiki/Smart_pointer
W informatyce inteligentny wskaźnik to abstrakcyjny typ danych, który symuluje wskaźnik, zapewniając jednocześnie dodatkowe funkcje, takie jak automatyczne wyrzucanie elementów bezużytecznych lub sprawdzanie granic. Te dodatkowe funkcje mają na celu ograniczenie błędów spowodowanych niewłaściwym użyciem wskaźników przy jednoczesnym zachowaniu wydajności. Inteligentne wskaźniki zwykle śledzą obiekty, które na nie wskazują, w celu zarządzania pamięcią. Niewłaściwe użycie wskaźników jest głównym źródłem błędów: ciągłe przydzielanie, dezalokacja i odwoływanie się, które musi być wykonane przez program napisany przy użyciu wskaźników, bardzo prawdopodobne jest, że nastąpi przeciek pamięci. Inteligentne wskaźniki próbują zapobiec wyciekom pamięci, czyniąc dezalokację zasobów automatyczną: gdy wskaźnik do obiektu (lub ostatni z serii wskaźników) zostanie zniszczony,
Niech T będzie klasą w tym samouczku Wskaźniki w C ++ można podzielić na 3 typy:
1) Surowe wskaźniki :
T a;
T * _ptr = &a;
Przechowują adres pamięci do lokalizacji w pamięci. Używaj ostrożnie, ponieważ programy stają się trudne do śledzenia.
Wskaźniki ze stałymi danymi lub adresem {Czytaj wstecz}
T a ;
const T * ptr1 = &a ;
T const * ptr1 = &a ;
Wskaźnik do typu danych T, który jest stałą. Oznacza to, że nie można zmienić typu danych za pomocą wskaźnika. tj *ptr1 = 19
; nie będzie działać. Ale możesz przesunąć wskaźnik. tj ptr1++ , ptr1--
; etc zadziała. Czytaj wstecz: wskaźnik na typ T, który jest const
T * const ptr2 ;
Stały wskaźnik do typu danych T. Oznacza to, że nie można przesunąć wskaźnika, ale można zmienić wartość wskazywaną przez wskaźnik. tzn. *ptr2 = 19
będzie działać, ale ptr2++ ; ptr2--
etc nie będzie działać. Czytaj wstecz: const wskaźnik do typu T.
const T * const ptr3 ;
Wskaźnik stały do stałego typu danych T. Oznacza to, że nie można przesunąć wskaźnika ani zmienić typu danych, aby był wskaźnikiem. tj. ptr3-- ; ptr3++ ; *ptr3 = 19;
nie będzie działać
3) Inteligentne wskaźniki : { #include <memory>
}
Współdzielony wskaźnik :
T a ;
//shared_ptr<T> shptr(new T) ; not recommended but works
shared_ptr<T> shptr = make_shared<T>(); // faster + exception safe
std::cout << shptr.use_count() ; // 1 // gives the number of "
things " pointing to it.
T * temp = shptr.get(); // gives a pointer to object
// shared_pointer used like a regular pointer to call member functions
shptr->memFn();
(*shptr).memFn();
//
shptr.reset() ; // frees the object pointed to be the ptr
shptr = nullptr ; // frees the object
shptr = make_shared<T>() ; // frees the original object and points to new object
Zaimplementowano za pomocą zliczania referencji, aby śledzić, ile „rzeczy” wskazuje na obiekt wskazywany przez wskaźnik. Gdy liczba ta spadnie do 0, obiekt jest automatycznie usuwany, tzn. Obiekt jest usuwany, gdy wszystkie share_ptr wskazujące na obiekt wykraczają poza zakres. Pozbywa się to bólu głowy związanego z usuwaniem obiektów przydzielonych za pomocą nowego.
Słaby wskaźnik: Pomaga radzić sobie z cyklicznym odniesieniem, które powstaje podczas używania wskaźnika wspólnego Jeśli masz dwa obiekty wskazywane przez dwa wspólne wskaźniki, a wewnętrzny wskaźnik wspólny wskazuje na siebie wskaźnik wspólny, wówczas będzie to odwołanie cykliczne, a obiekt nie będzie zostać usunięte, gdy wskaźniki udostępnione wykroczą poza zakres. Aby rozwiązać ten problem, zmień element wewnętrzny z shared_ptr na poor_ptr. Uwaga: Aby uzyskać dostęp do elementu wskazywanego przez słaby wskaźnik, użyj funkcji lock (), zwraca ono parametr słabą_ptr.
T a ;
shared_ptr<T> shr = make_shared<T>() ;
weak_ptr<T> wk = shr ; // initialize a weak_ptr from a shared_ptr
wk.lock()->memFn() ; // use lock to get a shared_ptr
// ^^^ Can lead to exception if the shared ptr has gone out of scope
if(!wk.expired()) wk.lock()->memFn() ;
// Check if shared ptr has gone out of scope before access
Zobacz: Kiedy przydatne jest std :: poor_ptr?
Unikalny wskaźnik: Lekki inteligentny wskaźnik z wyłączną własnością. Użyj, gdy wskaźnik wskazuje unikalne obiekty bez udostępniania obiektów między wskaźnikami.
unique_ptr<T> uptr(new T);
uptr->memFn();
//T * ptr = uptr.release(); // uptr becomes null and object is pointed to by ptr
uptr.reset() ; // deletes the object pointed to by uptr
Aby zmienić obiekt wskazany przez unikatowy ptr, użyj semantyki przenoszenia
unique_ptr<T> uptr1(new T);
unique_ptr<T> uptr2(new T);
uptr2 = std::move(uptr1);
// object pointed by uptr2 is deleted and
// object pointed by uptr1 is pointed to by uptr2
// uptr1 becomes null
Odniesienia: Zasadniczo mogą być traktowane jako wskaźniki const, tzn. Wskaźnik, który jest const i nie można go przenosić z lepszą składnią.
Zobacz: Jakie są różnice między zmienną wskaźnika i zmienną odniesienia w C ++?
r-value reference : reference to a temporary object
l-value reference : reference to an object whose address can be obtained
const reference : reference to a data type which is const and cannot be modified
Odniesienie: https://www.youtube.com/channel/UCEOGtxYTB6vo6MQ-WQ9W_nQ Dziękujemy Andre za wskazanie tego pytania.
Inteligentny wskaźnik to klasa, opakowanie normalnego wskaźnika. W przeciwieństwie do normalnych wskaźników, koło życia inteligentnego punktu opiera się na liczbie referencyjnej (ile razy przypisany jest obiekt inteligentnego wskaźnika). Tak więc za każdym razem, gdy inteligentny wskaźnik jest przypisywany do innego, wewnętrzny licznik referencyjny plus plus. I ilekroć obiekt wykracza poza zakres, licznik referencyjny minus minus.
Automatyczny wskaźnik, choć wygląda podobnie, całkowicie różni się od inteligentnego wskaźnika. Jest to wygodna klasa, która zwalnia zasób, gdy automatyczny obiekt wskaźnika wykracza poza zakres zmiennej. W pewnym stopniu sprawia, że wskaźnik (do dynamicznie alokowanej pamięci) działa podobnie do zmiennej stosu (statycznie alokowanej w czasie kompilacji).
Inteligentne wskaźniki to te, w których nie musisz się martwić o alokację pamięci, udostępnianie zasobów i transfer.
Możesz bardzo dobrze używać tych wskaźników w podobny sposób, jak każda alokacja działa w Javie. W java Garbage Collector wykonuje lewę, podczas gdy w Smart Pointers trik odbywa się przez Destructors.
Istniejące odpowiedzi są dobre, ale nie obejmują tego, co zrobić, gdy inteligentny wskaźnik nie jest (kompletną) odpowiedzią na problem, który próbujesz rozwiązać.
Między innymi (dobrze wyjaśnione w innych odpowiedziach) użycie inteligentnego wskaźnika jest możliwym rozwiązaniem Jak wykorzystać klasę abstrakcyjną jako typ zwracanej funkcji? który został oznaczony jako duplikat tego pytania. Jednak pierwszym pytaniem, które kusi cię, by wskazać abstrakcyjną (a właściwie dowolną) klasę bazową jako typ zwracany w C ++, jest „co tak naprawdę masz na myśli?”. Dobra dokumentacja (z dalszymi odniesieniami) programowania obiektowego idiomatycznego w C ++ (i czym różni się on od innych języków) w dokumentacji biblioteki kontenerów wskaźników wskaźników. Podsumowując, w C ++ musisz pomyśleć o własności. Które inteligentne wskaźniki pomagają, ale nie są jedynym rozwiązaniem lub zawsze kompletnym rozwiązaniem (nie dają kopii polimorficznej) i nie zawsze są rozwiązaniem, które chcesz ujawnić w interfejsie (a powrót funkcji brzmi okropnie bardzo podobny interfejs). Na przykład może wystarczyć zwrócenie referencji. Ale we wszystkich tych przypadkach (inteligentny wskaźnik, kontener wskaźnika lub po prostu zwracanie odwołania) zmieniłeś zwrot z wartości na pewną formę odniesienia. Jeśli naprawdę potrzebujesz kopii, może być konieczne dodanie większej liczby „idiomów” lub przejście poza idiomatyczne (lub inne) OOP w C ++ do bardziej ogólnego polimorfizmu przy użyciu bibliotek takich jak Adobe Poly lub Boost. .