Intelligente Zeiger (Modern C++)

In der modernen C++-Programmierung enthält die Standardbibliothek intelligente Zeiger, die verwendet werden, um sicherzustellen, dass Programme frei von Arbeitsspeicher und Ressourcenlecks sind und ausnahmesicher sind.

Anwendungsmöglichkeiten für intelligente Zeiger

Intelligente Zeiger werden im std Namespace in der <Speicherheaderdatei> definiert. Sie sind entscheidend für die RAII - oder Ressourcenerwerbsprogrammierungs-Idiom . 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++-Standardbibliothek 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 auf dem intelligenten Zeiger selbst.)

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

  3. Übergeben Sie einen unformatierten Zeiger an ein new-ed-Objekt im smarten Zeigerkonstruktor. (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 smarten Zeigerüberladung * und -> Operatoren ist nicht wesentlich langsamer als der direkte Zugriff auf die unformatierten Zeiger.

Intelligente Zeiger verfügen über eigene Memberfunktionen, auf die mithilfe der Schreibweise "Punkt" zugegriffen wird. Beispielsweise verfügen einige intelligente Zeiger der C++-Standardbibliothek über eine Zurücksetzungselementfunktion, 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. Intelligente Zeiger der C++-Standardbibliothek verfügen zu diesem Zweck über eine get Memberfunktion und CComPtr verfügen über ein öffentliches p Klassenmitglied. 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 intelligenter Zeiger

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++-Standardbibliothek

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 aus C++-Standardbibliothekssammlungen. Headerdatei: <memory>. Weitere Informationen finden Sie unter How to: Create and Use unique_ptr Instances and unique_ptr Class.

  • 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 How to: Create and Use shared_ptr Instances and shared_ptr Class.

  • 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 How to: Create and Use weak_ptr Instances and weak_ptr Class.

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 How to: Create and Use CComPtr and CComQIPtr Instances.

CComQIPtr-Klasse
Ähnelt CComPtr, stellt jedoch zusätzlich vereinfachte Syntax zum Aufrufen von QueryInterface in COM-Objekten bereit. Weitere Informationen finden Sie unter How to: Create and Use CComPtr and CComQIPtr Instances.

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

Neben intelligenten Zeigern für COM-Objekte definiert ATL auch intelligente Zeiger und Sammlungen intelligenter Zeiger für einfache alte C++-Objekte (POCO). Bei der klassischen Windows-Programmierung sind diese Typen hilfreiche Alternativen zu den C++-Standardbibliothekssammlungen, insbesondere, wenn die Codeübertragbarkeit nicht erforderlich ist oder Sie die Programmiermodelle 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
Smart pointer for objects that are assigned by using the C malloc function.

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