std:: shared_ptr
|
Definiert im Header
<memory>
|
||
|
template
<
class
T
>
class
shared_ptr
;
|
(seit C++11) | |
std::shared_ptr
ist ein intelligenter Zeiger, der gemeinsamen Besitz eines Objekts über einen Zeiger aufrechterhält. 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, wird zerstört; -
der letzte verbleibende
shared_ptr, der das Objekt besitzt, wird über operator= oder reset() ein anderer Zeiger zugewiesen.
Das Objekt wird mittels eines
delete-expression
oder eines benutzerdefinierten Löschers zerstört, der dem
shared_ptr
während der Konstruktion übergeben wird.
Ein
shared_ptr
kann die Eigentümerschaft eines Objekts teilen, während er auf ein anderes Objekt zeigt. Diese Funktion kann verwendet werden, um auf Member-Objekte zu zeigen, während das Objekt, zu dem sie gehören, im Besitz bleibt. Der gespeicherte Zeiger ist derjenige, auf den durch
get()
, die Dereferenzierungs- und Vergleichsoperatoren zugegriffen wird. Der verwaltete Zeiger ist derjenige, der an den Deleter übergeben wird, wenn die Verwendungsanzahl 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 Aliasing-Konstruktor zu seiner Erzeugung verwendet wurde).
Alle Spezialisierungen von
shared_ptr
erfüllen die Anforderungen von
CopyConstructible
,
CopyAssignable
und
LessThanComparable
und sind
kontextuell konvertierbar
zu
bool
.
Alle Memberfunktionen (einschließlich Kopierkonstruktor und Kopierzuweisung) können von mehreren Threads auf verschiedenen
shared_ptr
Objekten ohne zusätzliche Synchronisierung aufgerufen werden, selbst wenn diese Objekte Kopien sind und sich das Eigentum am selben Objekt teilen. Wenn mehrere Ausführungsthreads auf dasselbe
shared_ptr
Objekt ohne Synchronisierung zugreifen und einer dieser Zugriffe eine nicht-konstante Memberfunktion von
shared_ptr
verwendet, tritt ein Datenwettlauf auf; der
std::atomic<shared_ptr>
kann verwendet werden, um den Datenwettlauf zu verhindern.
Inhaltsverzeichnis |
Mitgliedertypen
| Mitgliedertyp | Definition | ||||
element_type
|
|
||||
weak_type
(seit C++17)
|
std:: weak_ptr < T > |
Memberfunktionen
Konstruiert neuen
shared_ptr
(öffentliche Elementfunktion) |
|
zerstört das verwaltete Objekt, falls keine weiteren
shared_ptr
darauf verweisen
(öffentliche Elementfunktion) |
|
weist den
shared_ptr
zu
(öffentliche Elementfunktion) |
|
Modifikatoren |
|
|
ersetzt das verwaltete Objekt
(öffentliche Elementfunktion) |
|
|
tauscht die verwalteten Objekte
(öffentliche Elementfunktion) |
|
Beobachter |
|
|
gibt den gespeicherten Zeiger zurück
(öffentliche Elementfunktion) |
|
|
dereferenziert den gespeicherten Zeiger
(öffentliche Elementfunktion) |
|
|
(C++17)
|
bietet indizierten Zugriff auf das gespeicherte Array
(öffentliche Elementfunktion) |
gibt die Anzahl der
shared_ptr
-Objekte zurück, die auf dasselbe verwaltete Objekt verweisen
(öffentliche Elementfunktion) |
|
|
(until C++20)
|
prüft, ob das verwaltete Objekt nur vom aktuellen
shared_ptr
-Objekt verwaltet wird
(öffentliche Elementfunktion) |
|
prüft, ob der gespeicherte Zeiger nicht null ist
(öffentliche Elementfunktion) |
|
|
bietet besitzerbasierte Ordnung von Shared Pointern
(öffentliche Elementfunktion) |
|
|
(C++26)
|
bietet besitzerbasiertes Hashing von Shared Pointern
(öffentliche Elementfunktion) |
|
(C++26)
|
bietet besitzerbasierte Gleichheitsvergleiche von Shared Pointern
(öffentliche Elementfunktion) |
Nicht-Member-Funktionen
|
erstellt einen shared_ptr, der ein neues Objekt verwaltet
(Funktions-Template) |
|
|
erstellt einen shared_ptr, der ein neues Objekt verwaltet, das mit einem Allokator alloziert wurde
(Funktions-Template) |
|
|
wendet
static_cast
,
dynamic_cast
,
const_cast
, oder
reinterpret_cast
auf den gespeicherten Zeiger an
(Funktions-Template) |
|
|
gibt den Deleter des angegebenen Typs zurück, falls vorhanden
(Funktions-Template) |
|
|
(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
(Funktions-Template) |
|
gibt den Wert des gespeicherten Zeigers an einen Ausgabestrom aus
(Funktions-Template) |
|
|
(C++11)
|
spezialisiert den
std::swap
Algorithmus
(Funktions-Template) |
spezialisiert atomare Operationen für
std::shared_ptr
(Funktionstemplate) |
Hilfsklassen
|
(C++20)
|
atomarer Shared Pointer
(Klassen-Template-Spezialisierung) |
|
(C++11)
|
Hash-Unterstützung für
std::shared_ptr
(Klassen-Template-Spezialisierung) |
Deduction guides (seit C++17)
Hinweise
Der Besitz eines Objekts kann nur durch Konstruktions- oder Zuweisungsfunktionen geteilt werden, die ein anderes
shared_ptr
-Objekt empfangen. Wenn ein neuer
shared_ptr
ausschließlich unter Verwendung des rohen zugrundeliegenden Zeigers erstellt wird, der von einem anderen
shared_ptr
gehalten wird, geht dieser neue
shared_ptr
davon aus, dass keine anderen
shared_ptr
-Instanzen das Objekt besitzen, das er hält. Dies führt dazu (sofern keine nachfolgende Zuweisung erfolgt), dass der Deleter während der Zerstörung wiederholt auf dasselbe Objekt angewendet wird.
std::shared_ptr
kann mit einem
unvollständigen Typ
T
verwendet werden. Allerdings dürfen der Konstruktor aus einem rohen Zeiger (
template
<
class
Y
>
shared_ptr
(
Y
*
)
) und die
template
<
class
Y
>
void
reset
(
Y
*
)
Member-Funktion 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, anstatt eines Objektzeigers. Dies wird manchmal verwendet, um eine dynamische Bibliothek oder ein Plugin geladen zu halten, solange irgendeine ihrer Funktionen referenziert wird:
void del(void(*)()) {} void fun() {} int main() { std::shared_ptr<void()> ee(fun, del); (*ee)(); }
Implementierungshinweise
In einer typischen Implementierung,
shared_ptr
enthält nur zwei Zeiger:
- der gespeicherte Zeiger (der von get() zurückgegebene);
- ein Zeiger auf den Kontrollblock .
Der Kontrollblock ist ein dynamisch allokiertes Objekt, das Folgendes enthält:
- entweder einen Zeiger auf das verwaltete Objekt oder das verwaltete Objekt selbst;
- den Deleter (typlöscht);
- den Allokator (typlöscht);
-
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 für sowohl den Kontrollblock als auch das verwaltete Objekt mit einer einzigen Allokation erstellt. Das verwaltete Objekt wird direkt in einem Datenelement des Kontrollblocks konstruiert. Wenn
shared_ptr
über einen der
shared_ptr
-Konstruktoren erstellt wird, müssen der verwaltete Objekt und der Kontrollblock separat allokiert werden. In diesem Fall speichert der Kontrollblock einen Zeiger auf das verwaltete Objekt.
Der vom
shared_ptr
direkt gehaltene Zeiger ist derjenige, der von
get()
zurückgegeben wird, während der im Kontrollblock gehaltene Zeiger/das Objekt derjenige ist, der gelöscht wird, wenn die Anzahl der gemeinsamen Besitzer null erreicht. Diese Zeiger sind nicht notwendigerweise gleich.
Der Destruktor von
shared_ptr
dekrementiert die Anzahl der gemeinsamen Eigentümer des Kontrollblocks. Wenn dieser Zähler null erreicht, ruft der Kontrollblock den Destruktor des verwalteten Objekts auf. Der Kontrollblock gibt sich selbst erst frei, wenn der
std::weak_ptr
-Zähler ebenfalls null erreicht.
In bestehenden Implementierungen wird die Anzahl der Weak Pointer erhöht ( [1] , [2] ), falls ein Shared Pointer auf denselben Kontrollblock existiert.
Um die Anforderungen an Thread-Sicherheit zu erfüllen, werden die Referenzzähler typischerweise mittels eines Äquivalents von std::atomic::fetch_add mit std::memory_order_relaxed inkrementiert (das Dekrementieren erfordert eine stärkere Ordnung zur sicheren Zerstörung des Kontrollblocks).
Beispiel
#include <chrono> #include <iostream> #include <memory> #include <mutex> #include <thread> using namespace std::chrono_literals; struct Base { Base() { std::cout << "Base::Base()\n"; } // Hinweis: nicht-virtueller Destruktor ist hier in Ordnung ~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; // threadsicher, selbst wenn der // gemeinsame use_count erhöht wird { static std::mutex io_mutex; std::lock_guard<std::mutex> lk(io_mutex); print("Lokaler Zeiger in einem Thread:", lp); } } int main() { std::shared_ptr<Base> p = std::make_shared<Derived>(); print("Erstellt einen gemeinsamen Derived (als Zeiger auf Base)", p); std::thread t1{thr, p}, t2{thr, p}, t3{thr, p}; p.reset(); // Besitzrecht von main freigeben print("Gemeinsamer Besitz zwischen 3 Threads und freigegebener Besitz von main:", p); t1.join(); t2.join(); t3.join(); std::cout << "Alle Threads abgeschlossen, der letzte hat Derived gelöscht.\n"; }
Mögliche Ausgabe:
Base::Base() Derived::Derived() Erstellt einen gemeinsamen Derived (als Zeiger auf Base) get() = 0x118ac30, use_count() = 1 Gemeinsamer Besitz zwischen 3 Threads und freigegebener Besitz von main: get() = 0, use_count() = 0 Lokaler Zeiger in einem Thread: get() = 0x118ac30, use_count() = 5 Lokaler Zeiger in einem Thread: get() = 0x118ac30, use_count() = 4 Lokaler Zeiger in einem Thread: get() = 0x118ac30, use_count() = 2 Derived::~Derived() Base::~Base() Alle Threads abgeschlossen, der letzte hat Derived gelöscht.
Beispiel
#include <iostream> #include <memory> struct MyObj { MyObj() { std::cout << "MyObj konstruiert\n"; } ~MyObj() { std::cout << "MyObj zerstört\n"; } }; struct Container : std::enable_shared_from_this<Container> // Hinweis: öffentliche Vererbung { std::shared_ptr<MyObj> memberObj; void CreateMember() { memberObj = std::make_shared<MyObj>(); } std::shared_ptr<MyObj> GetAsMyObj() { // Verwende einen Alias Shared Pointer für 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("Erstellen eines gemeinsamen Containers"); std::shared_ptr<Container> cont = std::make_shared<Container>(); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Erstellen von Mitgliedern"); cont->CreateMember(); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Erstellen eines weiteren gemeinsamen Containers"); 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("Kopieren des Alias-Objekts"); 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("Zurücksetzen von cont2"); cont2.reset(); DEMO(myobj1.use_count()); DEMO(myobj2.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Zurücksetzen von myobj2"); myobj2.reset(); DEMO(myobj1.use_count()); DEMO(cont.use_count()); DEMO(cont->memberObj.use_count()); COUT("Zurücksetzen cont"); cont.reset(); DEMO(myobj1.use_count()); DEMO(cont.use_count()); }
Ausgabe:
Erstellen eines gemeinsamen Containers cont.use_count() = 1 cont->memberObj.use_count() = 0 Erstellen eines Members MyObj konstruiert cont.use_count() = 1 cont->memberObj.use_count() = 1 Erstellen eines weiteren gemeinsamen Containers 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 Kopieren des Alias-Objekts 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 Zurücksetzen von cont2 myobj1.use_count() = 3 myobj2.use_count() = 3 cont.use_count() = 3 cont->memberObj.use_count() = 1 Zurücksetzen von myobj2 myobj1.use_count() = 2 cont.use_count() = 2 cont->memberObj.use_count() = 1 Zurücksetzen von cont myobj1.use_count() = 1 cont.use_count() = 0 MyObj zerstört
Siehe auch
|
(C++11)
|
Intelligenter Zeiger mit eindeutiger Objektbesitz-Semantik
(Klassentemplate) |
|
(C++11)
|
Schwacher Verweis auf ein von
std::shared_ptr
verwaltetes Objekt
(Klassentemplate) |