„Surowy” wskaźnik nie jest zarządzany. Oznacza to, że następujący wiersz:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... wycieknie pamięć, jeśli towarzyszenie deletenie 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_ptrjest 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 vectorzmianie rozmiaru żaden z obiektów nie zostanie przypadkowo usunięty podczas vectorkopiowania 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ść deleted.
To wprowadza problem: struktury podwójnie połączone kończą się referencjami kołowymi. Powiedzmy, że chcemy dodać parentwskaź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 deleted, 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 QObjectnich ma mechanizm, w którym zostaną one odpowiednio zniszczone bez potrzeby korzystania z deletenich przez użytkownika .
Nie wiem, jak QObjectbę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.