W C ++ jest wiele wskazówek, ale szczerze mówiąc za około 5 lat w programowaniu w C ++ (szczególnie w Qt Framework) używam tylko starego surowego wskaźnika:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
Wiem, że istnieje wiele innych „inteligentnych” wskaźników:
// shared pointer:
shared_ptr<SomeKindofObject> Object;
// unique pointer:
unique_ptr<SomeKindofObject> Object;
// weak pointer:
weak_ptr<SomeKindofObject> Object;
Ale nie mam najmniejszego pojęcia, co z nimi zrobić i co mogą mi zaoferować w porównaniu z surowymi wskazówkami.
Na przykład mam ten nagłówek klasy:
#ifndef LIBRARY
#define LIBRARY
class LIBRARY
{
public:
// Permanent list that will be updated from time to time where
// each items can be modified everywhere in the code:
QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings;
private:
// Temporary reader that will read something to put in the list
// and be quickly deleted:
QSettings *_reader;
// A dialog that will show something (just for the sake of example):
QDialog *_dialog;
};
#endif
Nie jest to oczywiście wyczerpujące, ale czy dla każdego z tych 3 wskaźników można pozostawić je „surowe”, czy powinienem użyć czegoś bardziej odpowiedniego?
I po raz drugi, jeśli pracodawca przeczyta kod, czy będzie surowy w kwestii jakich wskaźników używam, czy nie?
c++
programming-practices
pointers
qt
smart-pointer
CheshireChild
źródło
źródło
Odpowiedzi:
„Surowy” wskaźnik nie jest zarządzany. Oznacza to, że następujący wiersz:
... wycieknie pamięć, jeśli towarzyszenie
delete
nie zostanie wykonane we właściwym czasie.auto_ptr
W celu zminimalizowania tych przypadków
std::auto_ptr<>
wprowadzono. Jednak ze względu na ograniczenia C ++ przed standardem 2011, nadal bardzo łatwoauto_ptr
jest przeciekać pamięć. Jest to wystarczające w ograniczonych przypadkach, takich jak ten, jednak:Jednym z najsłabszych przypadków użycia są pojemniki. Wynika to z faktu, że jeśli
auto_ptr<>
zostanie wykonana kopia pliku, a stara kopia nie zostanie starannie zresetowana, pojemnik może usunąć wskaźnik i utracić dane.unique_ptr
Jako zamiennik C ++ 11 wprowadził
std::unique_ptr<>
:Taki
unique_ptr<>
zostanie poprawnie wyczyszczony, nawet jeśli zostanie przekazany między funkcjami. Robi to poprzez semantyczne reprezentowanie „własności” wskaźnika - „właściciel” go oczyszcza. Dzięki temu idealnie nadaje się do stosowania w pojemnikach:W przeciwieństwie do
auto_ptr<>
,unique_ptr<>
jest tu dobrze zachowany, a povector
zmianie rozmiaru żaden z obiektów nie zostanie przypadkowo usunięty podczasvector
kopiowania jego magazynu kopii zapasowych.shared_ptr
iweak_ptr
unique_ptr<>
jest to użyteczne, oczywiście, ale zdarzają się przypadki, w których dwie części bazy kodu mogą odnosić się do tego samego obiektu i kopiować wskaźnik dookoła, przy jednoczesnym zagwarantowaniu prawidłowego czyszczenia. Na przykład drzewo może wyglądać tak, gdy używaszstd::shared_ptr<>
:W takim przypadku możemy nawet zatrzymać wiele kopii węzła głównego, a drzewo zostanie odpowiednio wyczyszczone, gdy wszystkie kopie węzła głównego zostaną zniszczone.
Działa to, ponieważ każdy
shared_ptr<>
zachowuje nie tylko wskaźnik do obiektu, ale także liczbę referencji wszystkichshared_ptr<>
obiektów, które odnoszą się do tego samego wskaźnika. Po utworzeniu nowego liczba rośnie. Kiedy ktoś zostanie zniszczony, liczba spada. Gdy liczba osiągnie zero, wskaźnik będzie mieć wartośćdelete
d.To wprowadza problem: struktury podwójnie połączone kończą się referencjami kołowymi. Powiedzmy, że chcemy dodać
parent
wskaźnik do naszego drzewaNode
:Teraz, jeśli usuniemy
Node
, istnieje cykliczne odniesienie do niego. Nigdy nie będziedelete
d, ponieważ jego liczba referencyjna nigdy nie będzie równa zero.Aby rozwiązać ten problem, użyj
std::weak_ptr<>
:Teraz wszystko będzie działać poprawnie, a usunięcie węzła nie pozostawi zablokowanych odniesień do węzła nadrzędnego. Jednak sprawia, że chodzenie po drzewie jest nieco bardziej skomplikowane:
W ten sposób możesz zablokować odniesienie do węzła i masz uzasadnioną gwarancję, że nie zniknie ono podczas pracy nad nim, ponieważ trzymasz się jednego
shared_ptr<>
z nich.make_shared
imake_unique
Teraz, istnieją pewne drobne problemy z
shared_ptr<>
iunique_ptr<>
że należy się zająć. Problem mają następujące dwie linie:Jeśli
thrower()
zgłosi wyjątek, obie linie wyciekną pamięć. Co więcej,shared_ptr<>
liczba referencyjna utrzymuje się z dala od obiektu, na który wskazuje, a to może oznaczać drugi przydział). To zwykle nie jest pożądane.C ++ 11 zapewnia,
std::make_shared<>()
a C ++ 14 zapewniastd::make_unique<>()
rozwiązanie tego problemu:Teraz, w obu przypadkach, nawet jeśli
thrower()
zgłasza wyjątek, nie nastąpi wyciek pamięci. Jako bonus,make_shared<>()
ma możliwość utworzenia liczby referencyjnej w tym samym obszarze pamięci, co obiekt zarządzany, który może być zarówno szybszy, jak i może zaoszczędzić kilka bajtów pamięci, dając jednocześnie wyjątkową gwarancję bezpieczeństwa!Uwagi na temat Qt
Należy jednak zauważyć, że Qt, który musi obsługiwać kompilatory w wersjach wcześniejszych niż C ++ 11, ma swój własny model zbierania elementów bezużytecznych: wiele z
QObject
nich ma mechanizm, w którym zostaną one odpowiednio zniszczone bez potrzeby korzystania zdelete
nich przez użytkownika .Nie wiem, jak
QObject
będzie się zachowywać, gdy będzie zarządzany przez wskaźniki zarządzane przez C ++ 11, więc nie mogę powiedzieć, żeshared_ptr<QDialog>
to dobry pomysł. Nie mam wystarczającego doświadczenia z Qt, aby powiedzieć na pewno, ale wierzę, że Qt5 został dostosowany do tego przypadku użycia.źródło
shared_ptr
Jest to osobny obiekt - oddzielny przydział - odnew
obiektu ed. Istnieją w różnych lokalizacjach.make_shared
ma możliwość złożenia ich w jednym miejscu, co poprawia między innymi lokalizację pamięci podręcznej.shared_ptr
jest przedmiotem. Aby zarządzać obiektem, musi on przydzielić obiekt (liczba referencyjna (słaby + silny) + niszczyciel).make_shared
umożliwia przydzielenie tego i zarządzanego obiektu jako jednego elementu.unique_ptr
nie korzysta z nich, więc nie ma żadnej odpowiedniej korzyści, oprócz upewnienia się, że obiekt jest zawsze własnością inteligentnego wskaźnika. Nawiasem mówiąc , można mieć obiekt,shared_ptr
który jest właścicielem obiektu bazowego i reprezentujenullptr
, lub który nie jest właścicielem i reprezentuje wskaźnik inny niż null.shared_ptr
robi: 1. Dzieli własność jakiegoś obiektu (reprezentowanego przez wewnętrzny dynamicznie przydzielany obiekt o słabej i silnej liczbie referencyjnej, a także usuwający) . 2. Zawiera wskaźnik. Te dwie części są niezależne.make_unique
imake_shared
oba upewnij się, że przydzielony obiekt jest bezpiecznie umieszczony w inteligentnym wskaźniku. Ponadtomake_shared
pozwala przydzielić obiekt własności i zarządzany wskaźnik razem.