std::shared_ptr
| Definiert in Header <memory> |
||
| template< class T > class shared_ptr; |
(seit C++11) | |
std::shared_ptr ist ein Smart-Pointer, der eine geteilte Besitzrechte an einem Objekt über einen Zeiger verwaltet. Mehrere shared_ptr-Objekte können dasselbe Objekt besitzen. Das Objekt wird zerstört und sein Speicher freigegeben, wenn eines der folgenden Ereignisse eintritt:
- der letzte verbleibende
shared_ptr, der das Objekt besitzt, zerstört wird; - dem letzten verbleibenden
shared_ptr, der das Objekt besitzt, über operator= oder reset() ein anderer Zeiger zugewiesen wird.
Das Objekt wird mit einem delete-Ausdruck oder einem benutzerdefinierten Deleter, der shared_ptr während der Konstruktion übergeben wird, zerstört.
Ein shared_ptr kann den Besitz eines Objekts teilen, während er einen Zeiger auf ein anderes Objekt speichert. Diese Funktion kann verwendet werden, um auf Member-Objekte zu zeigen, während das Objekt, zu dem sie gehören, besessen wird. Der gespeicherte Zeiger ist derjenige, auf den mit get(), den Dereferenzierungs- und Vergleichsoperatoren zugegriffen wird. Der verwaltete Zeiger ist derjenige, der an den Deleter übergeben wird, wenn die Gebrauchszählung Null erreicht.
Ein shared_ptr kann auch keine Objekte besitzen; in diesem Fall wird er als leer bezeichnet (ein leerer shared_ptr kann einen nicht-null gespeicherten Zeiger haben, wenn der Alias-Konstruktor zu seiner Erstellung verwendet wurde).
Alle Spezialisierungen von shared_ptr erfüllen die Anforderungen von CopyConstructible, CopyAssignable und LessThanComparable und sind kontextuell konvertierbar zu bool.
Alle Member-Funktionen (einschließlich Kopierkonstruktor und Kopierzuweisung) können von mehreren Threads auf unterschiedliche shared_ptr-Objekte aufgerufen werden, ohne zusätzliche Synchronisation, auch wenn diese Objekte Kopien sind und den Besitz desselben Objekts teilen. Wenn mehrere Threads auf dasselbe shared_ptr-Objekt ohne Synchronisation zugreifen und einer dieser Zugriffe eine nicht-const Member-Funktion von shared_ptr verwendet, tritt ein Datenrennen auf; std::atomic<shared_ptr> kann verwendet werden, um das Datenrennen zu verhindern.
Inhalt |
[edit] Member-Typen
| Mitgliedertyp | Definition | ||||
element_type
|
| ||||
weak_type (seit C++17) |
std::weak_ptr<T> |
[edit] Memberfunktionen
erstellt einen neuen shared_ptr(public member function) | |
zerstört das besessene Objekt, wenn keine weiteren shared_ptrs darauf verweisen(public member function) | |
weist den shared_ptr zu(public member function) | |
Modifizierer | |
| Ersetzt das verwaltete Objekt (public member function) | |
| Vertauscht die verwalteten Objekte (public member function) | |
Observer | |
| gibt den gespeicherten Zeiger zurück (public member function) | |
| dereferenziert den gespeicherten Zeiger (public member function) | |
| (C++17) |
ermöglicht den indizierten Zugriff auf das gespeicherte Array (public member function) |
gibt die Anzahl der shared_ptr-Objekte zurück, die auf dasselbe verwaltete Objekt verweisen(public member function) | |
| (bis C++20) |
prüft, ob das verwaltete Objekt nur vom aktuellen shared_ptr-Objekt verwaltet wird(public member function) |
| prüft, ob der gespeicherte Zeiger nicht null ist (public member function) | |
| bietet inhaltsbasiertes Ordering von geteilten Zeigern (public member function) | |
| (C++26) |
ermöglicht besitzerbasiertes Hashing von shared_ptr (public member function) |
| (C++26) |
ermöglicht besitzerbasierten Gleichheitsvergleich von shared_ptr (public member function) |
[edit] Nicht-Member-Funktionen
| erstellt einen geteilten Zeiger, der ein neues Objekt verwaltet (Funktionsschablone) | |
| erstellt einen geteilten Zeiger, der ein neues Objekt verwaltet, das mithilfe eines Allokators zugewiesen wurde (Funktionsschablone) | |
| wendet static_cast, dynamic_cast, const_cast oder reinterpret_cast auf den gespeicherten Zeiger an (Funktionsschablone) | |
| gibt den Deleter des angegebenen Typs zurück, falls vorhanden (Funktionsschablone) | |
| (entfernt in C++20)(entfernt in C++20)(entfernt in C++20)(entfernt in C++20)(entfernt in C++20)(C++20) |
vergleicht mit einem anderen shared_ptr oder mit nullptr(Funktionsschablone) |
| gibt den Wert des gespeicherten Zeigers an einen Ausgabestrom aus (Funktionsschablone) | |
| (C++11) |
spezialisiert den Algorithmus std::swap (Funktionsschablone) |
spezialisiert atomare Operationen für std::shared_ptr(Funktionsschablone) |
[edit] Hilfsklassen
| (C++20) |
atomarer geteilter Zeiger (Klassenvorlagenspezialisierung) |
| (C++11) |
Hash-Unterstützung für std::shared_ptr (Klassentemplate-Spezialisierung) |
[edit] Deduktionsleitfäden (seit C++17)
[edit] Hinweise
Der Besitz eines Objekts kann nur durch Kopierkonstruktion oder Kopierzuweisung seines Wertes an einen anderen shared_ptr mit einem anderen shared_ptr geteilt werden. Das Erstellen eines neuen shared_ptr mit dem rohen zugrundeliegenden Zeiger eines anderen shared_ptr führt zu undefiniertem Verhalten.
std::shared_ptr kann mit einem unvollständigen Typ T verwendet werden. Der Konstruktor von einem rohen Zeiger (template<class Y> shared_ptr(Y*)) und die Member-Funktion template<class Y> void reset(Y*) dürfen jedoch nur mit einem Zeiger auf einen vollständigen Typ aufgerufen werden (beachten Sie, dass std::unique_ptr aus einem rohen Zeiger auf einen unvollständigen Typ konstruiert werden kann).
Das T in std::shared_ptr<T> kann ein Funktionstyp sein: In diesem Fall verwaltet es einen Zeiger auf eine Funktion, nicht einen Objektzeiger. Dies wird manchmal verwendet, um eine dynamische Bibliothek oder ein Plugin geladen zu halten, solange auf eine seiner Funktionen zugegriffen wird
void del(void(*)()) {} void fun() {} int main() { std::shared_ptr<void()> ee(fun, del); (*ee)(); }
[edit] Implementierungshinweise
In einer typischen Implementierung hält shared_ptr nur zwei Zeiger
- den gespeicherten Zeiger (der von get() zurückgegeben wird);
- einen Zeiger auf einen Kontrollblock.
Der Kontrollblock ist ein dynamisch zugeordnetes Objekt, das Folgendes enthält:
- entweder einen Zeiger auf das verwaltete Objekt oder das verwaltete Objekt selbst;
- den Deleter (typ-erased);
- den Allocator (typ-erased);
- die Anzahl der
shared_ptrs, die das verwaltete Objekt besitzen; - die Anzahl der
weak_ptrs, die auf das verwaltete Objekt verweisen.
Wenn shared_ptr durch Aufruf von std::make_shared oder std::allocate_shared erstellt wird, wird der Speicher sowohl für den Kontrollblock als auch für das verwaltete Objekt mit einer einzigen Allokation erstellt. Das verwaltete Objekt wird direkt in einem Datenmember des Kontrollblocks konstruiert. Wenn shared_ptr über einen der shared_ptr-Konstruktoren erstellt wird, müssen das verwaltete Objekt und der Kontrollblock separat allokiert werden. In diesem Fall speichert der Kontrollblock einen Zeiger auf das verwaltete Objekt.
Der direkt von shared_ptr gehaltene Zeiger ist derjenige, der von get() zurückgegeben wird, während der Zeiger/das Objekt, das vom Kontrollblock gehalten wird, dasjenige ist, das zerstört wird, wenn die Anzahl der gemeinsamen Besitzer Null erreicht. Diese Zeiger müssen nicht unbedingt gleich sein.
Der Destruktor von shared_ptr dekrementiert die Anzahl der gemeinsamen Besitzer des Kontrollblocks. Wenn dieser Zähler Null erreicht, ruft der Kontrollblock den Destruktor des verwalteten Objekts auf. Der Kontrollblock gibt sich selbst nicht frei, bis auch der std::weak_ptr-Zähler Null erreicht.
In existierenden Implementierungen wird die Anzahl der Weak-Pointer inkrementiert ([1], [2]), wenn ein Shared-Pointer auf denselben Kontrollblock existiert.
Um die Thread-Sicherheitsanforderungen zu erfüllen, werden die Referenzzähler typischerweise mithilfe eines Äquivalents von std::atomic::fetch_add mit std::memory_order_relaxed inkrementiert (das Dekrementieren erfordert eine stärkere Ordnung, um den Kontrollblock sicher zu zerstören).
[edit] Beispiel
#include <chrono> #include <iostream> #include <memory> #include <mutex> #include <thread> using namespace std::chrono_literals; struct Base { Base() { std::cout << "Base::Base()\n"; } // Note: non-virtual destructor is OK here ~Base() { std::cout << "Base::~Base()\n"; } }; struct Derived : public Base { Derived() { std::cout << "Derived::Derived()\n"; } ~Derived() { std::cout << "Derived::~Derived()\n"; } }; void print(auto rem, std::shared_ptr<Base> const& sp) { std::cout << rem << "\n\tget() = " << sp.get() << ", use_count() = " << sp.use_count() << '\n'; } void thr(std::shared_ptr<Base> p) { std::this_thread::sleep_for(987ms); std::shared_ptr<Base> lp = p; // thread-safe, even though the // shared use_count is incremented { static std::mutex io_mutex; std::lock_guard<std::mutex> lk(io_mutex); print("Local pointer in a thread:", lp); } } int main() { std::shared_ptr<Base> p = std::make_shared<Derived>(); print("Created a shared Derived (as a pointer to Base)", p); std::thread t1{thr, p}, t2{thr, p}, t3{thr, p}; p.reset(); // release ownership from main print("Shared ownership between 3 threads and released ownership from main:", p); t1.join(); t2.join(); t3.join(); std::cout << "All threads completed, the last one deleted Derived.\n"; }
Mögliche Ausgabe
Base::Base() Derived::Derived() Created a shared Derived (as a pointer to Base) get() = 0x118ac30, use_count() = 1 Shared ownership between 3 threads and released ownership from main: get() = 0, use_count() = 0 Local pointer in a thread: get() = 0x118ac30, use_count() = 5 Local pointer in a thread: get() = 0x118ac30, use_count() = 4 Local pointer in a thread: get() = 0x118ac30, use_count() = 2 Derived::~Derived() Base::~Base() All threads completed, the last one deleted Derived.
[edit] Beispiel
#include <iostream> #include <memory> struct MyObj { MyObj() { std::cout << "MyObj constructed\n"; } ~MyObj() { std::cout << "MyObj destructed\n"; } }; struct Container : std::enable_shared_from_this<Container> // note: public inheritance { std::shared_ptr<MyObj> memberObj; void CreateMember() { memberObj = std::make_shared<MyObj>(); } std::shared_ptr<MyObj> GetAsMyObj() { // Use an alias shared ptr for member return std::shared_ptr<MyObj>(shared_from_this(), memberObj.get()); } }; #define COUT(str) std::cout << '\n' << str << '\n' #define DEMO(...) std::cout << #__VA_ARGS__ << " = " << __VA_ARGS__ << '\n' int main() { COUT("Creating shared container"); std::shared_ptr<Container> cont = std::make_shared<Container>(); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Creating member"); cont->CreateMember(); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Creating another shared container"); std::shared_ptr<Container> cont2 = cont; DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); DEMO(cont2.use_count()); DEMO(cont2->memberObj.use_count()); COUT("GetAsMyObj"); std::shared_ptr<MyObj> myobj1 = cont->GetAsMyObj(); DEMO(myobj1.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); DEMO(cont2.use_count()); DEMO(cont2->memberObj.use_count()); COUT("Copying alias obj"); std::shared_ptr<MyObj> myobj2 = myobj1; DEMO(myobj1.use_count()); DEMO(myobj2.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); DEMO(cont2.use_count()); DEMO(cont2->memberObj.use_count()); COUT("Resetting cont2"); cont2.reset(); DEMO(myobj1.use_count()); DEMO(myobj2.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Resetting myobj2"); myobj2.reset(); DEMO(myobj1.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Resetting cont"); cont.reset(); DEMO(myobj1.use_count()); DEMO(cont.use_count()); }
Ausgabe
Creating shared container cont.use_count() = 1 cont->memberObj.use_count() = 0 Creating member MyObj constructed cont.use_count() = 1 cont->memberObj.use_count() = 1 Creating another shared container cont.use_count() = 2 cont->memberObj.use_count() = 1 cont2.use_count() = 2 cont2->memberObj.use_count() = 1 GetAsMyObj myobj1.use_count() = 3 cont.use_count() = 3 cont->memberObj.use_count() = 1 cont2.use_count() = 3 cont2->memberObj.use_count() = 1 Copying alias obj myobj1.use_count() = 4 myobj2.use_count() = 4 cont.use_count() = 4 cont->memberObj.use_count() = 1 cont2.use_count() = 4 cont2->memberObj.use_count() = 1 Resetting cont2 myobj1.use_count() = 3 myobj2.use_count() = 3 cont.use_count() = 3 cont->memberObj.use_count() = 1 Resetting myobj2 myobj1.use_count() = 2 cont.use_count() = 2 cont->memberObj.use_count() = 1 Resetting cont myobj1.use_count() = 1 cont.use_count() = 0 MyObj destructed
[edit] Siehe auch
| (C++11) |
intelligenter Zeiger mit semantisch eindeutigem Objektbesitz (Klassen-Template) |
| (C++11) |
schwache Referenz auf ein von std::shared_ptr verwaltetes Objekt (Klassen-Template) |