„Surowy” wskaźnik nie jest zarządzany. Oznacza to, że następujący wiersz:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... 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 łatwo auto_ptr
jest przeciekać pamięć. Jest to wystarczające w ograniczonych przypadkach, takich jak ten, jednak:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
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<>
:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
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:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
W przeciwieństwie do auto_ptr<>
, unique_ptr<>
jest tu dobrze zachowany, a po vector
zmianie rozmiaru żaden z obiektów nie zostanie przypadkowo usunięty podczas vector
kopiowania jego magazynu kopii zapasowych.
shared_ptr
i weak_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żywasz std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
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 wszystkich shared_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 drzewa Node
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Teraz, jeśli usuniemy Node
, istnieje cykliczne odniesienie do niego. Nigdy nie będzie delete
d, ponieważ jego liczba referencyjna nigdy nie będzie równa zero.
Aby rozwiązać ten problem, użyj std::weak_ptr<>
:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
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:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
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
i make_unique
Teraz, istnieją pewne drobne problemy z shared_ptr<>
i unique_ptr<>
że należy się zająć. Problem mają następujące dwie linie:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
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 zapewnia std::make_unique<>()
rozwiązanie tego problemu:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
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 z delete
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ć, że shared_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.