Intelligente Zeiger (Modern C++)

In der modernen C++-Programmierung schließt die Standard Bibliothek intelligente Zeiger ein, die verwendet werden, um sicherzustellen, dass Programme nicht über genügend Arbeitsspeicher und Ressourcen Lecks verfügen und Ausnahme sicher sind.

Anwendungsmöglichkeiten für intelligente Zeiger

Intelligente Zeiger werden im- std Namespace in der <memory> Header Datei definiert. Sie sind für den RAII oder die Initialisierungs Programmierung mit der Ressourcen Erfassung von entscheidender Bedeutung. Das wichtigste Ziel dieses Technik ist sicherzustellen, dass die Ressourcenerfassung zur gleichen Zeit erfolgt wie die Initialisierung des Objekts, damit alle Ressourcen für das Objekt in einer Codezeile erstellt und vorbereitet werden können. Praktisch ist es das Hauptprinzip von RAII, die Verfügung über jede auf dem Heap zugeordnete Ressource (beispielsweise dynamisch zugeordneter Arbeitsspeicher oder Systemobjekthandles) einem auf dem Stapel zugeordneten Objekt zu übertragen, dessen Destruktor sowohl den Code enthält, um die Ressource zu löschen oder freizugeben, als auch jeden zugehörigen Bereinigungscode.

Wenn Sie einen Rohzeiger oder ein Ressourcenhandle für eine aktuelle Ressource initialisieren, sollten Sie den Zeiger in den meisten Fällen sofort einem intelligenten Zeiger zuweisen. In modernem C++ werden Rohzeiger nur in kleinen Codeblöcken mit begrenztem Gültigkeitsbereich, in Schleifen oder Hilfsfunktionen verwendet, in denen Leistung ausschlaggebend ist und keine Verwirrung über den Besitzer entstehen kann.

Im folgenden Beispiel wird die Deklaration eines Rohzeigers mit der eines intelligenten Zeigers verglichen.

void UseRawPointer()
{
    // Using a raw pointer -- not recommended.
    Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); 

    // Use pSong...

    // Don't forget to delete!
    delete pSong;   
}


void UseSmartPointer()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));

    // Use song2...
    wstring s = song2->duration_;
    //...

} // song2 is deleted automatically here.

Wie im Beispiel gezeigt, ist ein intelligenter Zeiger eine Klassenvorlage, die auf dem Stapel deklariert wird. Die Initialisierung erfolgt mit einem Rohzeiger auf ein auf dem Stapel zugeordnetes Objekt. Nachdem der intelligente Zeiger initialisiert wurde, besitzt er den Rohzeiger. Dies bedeutet, dass der intelligente Zeiger für das Löschen des Arbeitsspeichers zuständig ist, den der Rohzeiger angibt. Der Destruktor des intelligenten Zeigers enthält den Aufruf zum Löschen, und weil der intelligente Zeiger auf dem Stapel deklariert wurde, wird sein Destruktor aufgerufen, sobald der intelligente Zeiger ungültig wird, auch wenn eine Ausnahme irgendwo weiter oben im Stapel ausgelöst wird.

Greifen Sie auf den gekapselten Zeiger mit den vertrauten Zeigeroperatoren -> und * zu, die von der Klasse des intelligenten Zeigers so überladen werden, dass der gekapselte Rohzeiger zurückgegeben wird.

Die Wirkungsweise eines intelligenten C++-Zeigers ähnelt dem Vorgehen bei der Objekterstellung in Sprachen wie C#: Sie erstellen das Objekt und überlassen es dann dem System, das Objekt zur richtigen Zeit zu löschen. Der Unterschied besteht darin, dass im Hintergrund keine separate Speicherbereinigung ausgeführt wird – der Arbeitsspeicher wird durch die C++-Standardregeln für den Gültigkeitsbereich verwaltet, sodass die Laufzeitumgebung schneller und effizienter ist.

Wichtig

Erstellen Sie intelligente Zeiger immer in einer eigenen Codezeile und nie in einer Parameterliste, damit aufgrund bestimmter Speicherbelegungsregeln für Parameterlisten kein kleines Ressourcenleck auftritt.

Das folgende Beispiel zeigt, wie ein unique_ptr intelligenter Zeigertyp aus der C++-Standard Bibliothek verwendet werden kann, um einen Zeiger auf ein großes Objekt zu kapseln.


class LargeObject
{
public:
    void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

} //pLarge is deleted automatically when function block goes out of scope.

Dieses Beispiel verdeutlicht die folgenden wesentlichen Schritte für die Verwendung von intelligenten Zeigern.

  1. Deklarieren Sie den intelligenten Zeiger als automatische (lokale) Variable. (Verwenden Sie den- new oder- malloc Ausdruck nicht für den intelligenten Zeiger selbst.)

  2. Als Typparameter geben Sie den Typ an, auf den der gekapselte Zeiger zeigt.

  3. Übergeben Sie einen rohzeiger an ein new -Ed-Objekt im smarttagkonstruktor. (Einige Hilfsfunktionen oder Konstruktoren für intelligente Zeiger übernehmen das für Sie.)

  4. Verwenden Sie die überladenen Operatoren -> und * für den Zugriff auf das Objekt.

  5. Lassen Sie den intelligenten Zeiger das Objekt löschen.

Intelligente Zeiger sind dafür konzipiert, im Hinblick auf Leistung und Arbeitsspeicher so effizient wie möglich sein. Beispielsweise ist der einzige Datenmember in unique_ptr der gekapselte Zeiger. Dies bedeutet, dass unique_ptr genau die gleiche Größe hat wie dieser Zeiger, entweder vier oder acht Bytes. Der Zugriff auf den gekapselten Zeiger mithilfe der überladenen *-und->-Operatoren von intelligenten Zeigern ist nicht wesentlich langsamer als der direkte Zugriff auf die

Intelligente Zeiger verfügen über eigene Member-Funktionen, auf die Sie über die "Punkt"-Notation zugreifen. Beispielsweise verfügen einige intelligente Zeiger der C++-Standard Bibliothek über eine Reset Member-Funktion, die den Besitz des Zeigers freigibt. Dies ist hilfreich, wenn Sie den Arbeitsspeicher des intelligenten Zeigers freigeben möchten, bevor dieser ungültig wird, wie im folgenden Beispiel gezeigt.

void SmartPointerDemo2()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Free the memory before we exit function block.
    pLarge.reset();

    // Do some other work...

}

Intelligente Zeiger stellen normalerweise eine Möglichkeit für den direkten Zugriff auf ihre Rohzeiger bereit. Die intelligenten Zeiger der C++-Standard Bibliothek haben get zu diesem Zweck eine Member-Funktion und haben CComPtr einen öffentlichen p Klassenmember. Wenn Sie direkten Zugriff auf den zugrunde liegende Zeiger bereitstellen, können Sie den intelligenten Zeiger verwenden, um Arbeitsspeicher in Ihrem eigenen Code zu verwalten, und Sie können den Rohzeiger weiterhin an Code übergeben, der keine intelligenten Zeiger unterstützt.

void SmartPointerDemo4()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass raw pointer to a legacy API
    LegacyLargeObjectFunction(pLarge.get());    
}

Arten von intelligenten Zeigern

Im folgenden Abschnitt werden die in der Windows-Programmierumgebung verfügbaren verschiedenen Arten von intelligenten Zeigern aufgeführt, und es wird beschrieben, wann sie zu verwenden sind.

Intelligente Zeiger der C++-Standard Bibliothek

Verwenden Sie vorrangig diese intelligenten Zeiger zum Kapseln von Zeigern auf einfache alte C++-Objekte (Plain Old CLR Objects, POCOs).

  • unique_ptr
    Ermöglicht genau einen Besitzer für den zugrunde liegenden Zeiger. Verwenden Sie diesen Zeiger als Standardwahl für POCOs, es sei denn, Sie sind sicher, dass Sie einen shared_ptr benötigen. Kann zu einem neuen Besitzer verschoben werden, kann aber nicht kopiert oder freigegeben werden. Ersetzt auto_ptr, der veraltet ist. Ist vergleichbar mit boost::scoped_ptr. unique_ptr ist klein und effizient. die Größe ist ein Zeiger und unterstützt Rvalue-Verweise für schnelles Einfügen und Abrufen von C++-Standard Bibliotheks Auflistungen. Headerdatei: <memory>. Weitere Informationen finden Sie unter Vorgehens Weise: Erstellen und Verwenden von unique_ptr Instanzen und unique_ptr Klasse.

  • shared_ptr
    Intelligenter Zeiger mit Referenzzählung. Verwenden Sie diesen Zeiger, wenn Sie mehreren Besitzern einen Rohzeiger zuweisen möchten. Beispiel: Sie geben eine Kopie eines Zeigers von einem Container zurück, möchten aber das Original behalten. Der Rohzeiger wird erst gelöscht, wenn alle shared_ptr-Besitzer den Gültigkeitsbereich verlassen haben oder auf andere Weise nicht mehr Besitzer sind. Die Größe beträgt zwei Zeiger; einen für das Objekt und einen für den freigegebenen Kontrollblock, der den Referenzzähler enthält. Headerdatei: <memory>. Weitere Informationen finden Sie unter Vorgehens Weise: Erstellen und Verwenden von shared_ptr Instanzen und shared_ptr Klasse.

  • weak_ptr
    Spezielle intelligente Zeiger in Verbindung mit shared_ptr. Ein weak_ptr ermöglicht den Zugriff auf ein Objekt, das einer oder mehreren shared_ptr-Instanzen gehört, ist aber nicht an der Referenzzählung beteiligt ist. Verwenden Sie diesen Zeiger, wenn Sie ein Objekt beobachten möchten, dieses aber nicht gültig bleiben muss. Ist in einigen Fällen erforderlich, um Zirkelverweise zwischen shared_ptr-Instanzen zu unterbrechen. Headerdatei: <memory>. Weitere Informationen finden Sie unter Vorgehens Weise: Erstellen und Verwenden von weak_ptr Instanzen und weak_ptr Klasse.

Intelligente Zeiger für COM-Objekte (klassische Windows-Programmierung)

Wenn Sie mit COM-Objekten arbeiten, sollten Sie Schnittstellenzeiger mit einem geeigneten Typ eines intelligenten Zeigers kapseln. Die ATL (Active Template Library) definiert mehrere intelligente Zeiger für verschiedene Zwecke. Sie können auch den Typ _com_ptr_t eines intelligenten Zeigers verwenden, den der Compiler einsetzt, wenn er Wrapperklassen von .tlb-Dateien erstellt. Er ist die beste Wahl, wenn Sie die ATL-Headerdateien nicht einschließen möchten.

CComPtr-Klasse
Verwenden Sie diesen, sofern Sie ATL nicht verwenden können. Führt Referenzzählung mithilfe der Methoden AddRef und Release aus. Weitere Informationen finden Sie unter Gewusst wie: Erstellen und Verwenden von CComPtr-und CComQIPtr-Instanzen.

CComQIPtr-Klasse
Ähnelt CComPtr, stellt jedoch zusätzlich vereinfachte Syntax zum Aufrufen von QueryInterface in COM-Objekten bereit. Weitere Informationen finden Sie unter Gewusst wie: Erstellen und Verwenden von CComPtr-und CComQIPtr-Instanzen.

Ccomheapptr-Klasse
Intelligenter Zeiger auf Objekte, die CoTaskMemFree verwenden, um Arbeitsspeicher freizugeben.

CComGITPtr-Klasse
Intelligenter Zeiger für Schnittstellen, die aus der globalen Schnittstellentabelle (Global Interface Table, Git) abgerufen werden.

_com_ptr_t-Klasse
Ähnelt CComQIPtr bezüglich der Funktionalität, ist jedoch nicht von ATL-Headern abhängig.

Intelligente ATL-Zeiger für poco-Objekte

Zusätzlich zu intelligenten Zeigern für COM-Objekte definiert ATL auch intelligente Zeiger und Auflistungen von intelligenten Zeigern für Plain Old C++ Objects (poco). Bei der klassischen Windows-Programmierung sind diese Typen nützliche Alternativen zu den C++-Standard Bibliotheks Auflistungen, insbesondere wenn die Code Portabilität nicht erforderlich ist oder wenn Sie die Programmier Modelle der C++-Standardbibliothek und ATL nicht kombinieren möchten.

Cautoptr-Klasse
Intelligenter Zeiger, der eindeutigen Besitz erzwingt, indem er den Besitz auf die Kopie überträgt. Vergleichbar mit der veralteten std::auto_ptr-Klasse.

Cheapptr-Klasse
Intelligenter Zeiger für Objekte, die mithilfe der C- malloc -Funktion zugewiesen werden.

Cautovectorptr-Klasse
Intelligenter Zeiger für Arrays, die mit new[] zugeordnet werden.

Cautoptrarray-Klasse
Klasse, die ein Array mit CAutoPtr-Elementen kapselt.

Cautoptrlist-Klasse
Klasse, die Methoden zum Bearbeiten einer Liste von CAutoPtr-Knoten kapselt.

Siehe auch

Zeiger
C++-Programmiersprachenreferenz
C++-Standardbibliothek