Freigeben über


Intelligente Zeiger (Modern C++)

In der modernen C++-Programmierung enthält die Standardbibliothek intelligente Zeiger, die sicherstellen sollen, dass Programme ausnahmensicher und frei von Speicher- und Ressourcenlecks sind.

Anwendungsmöglichkeiten für intelligente Zeiger

Intelligente Zeiger werden im std-Namespace in der <memory>-Headerdatei definiert. Sie sind für die Programmiertechnik RAII oder Resource Acquisition Is Initialialization (Ressourcenbelegung ist Initialisierung) entscheidend. 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.

Im folgenden Beispiel wird gezeigt, wie ein intelligenter Zeiger vom Typ unique_ptr aus der Standardvorlagenbibliothek 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 für den intelligenten Zeiger selbst nicht den Ausdruck new oder malloc.)

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

  3. Übergeben Sie einen Rohzeiger auf ein als new deklariertes Objekt im Konstruktor des intelligenten Zeigers. (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 gekapselte Zeiger mithilfe der überladenen Methoden * und -> des intelligenten Zeigers ist nicht wesentlich langsamer als der direkte Zugriff auf Rohzeiger.

Intelligente Zeiger verfügen über eigene Memberfunktionen, auf die mithilfe der Punktnotation zugegriffen wird. Beispielsweise verfügen einige intelligente Zeiger der STL über eine Rücksetzungsmemberfunktion, 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 STL verfügen zu diesem Zweck über eine get-Memberfunktion, und CComPtr besitzt 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++-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; rvalue-Referenzen für schnelles Einfügen und Abrufen in STL-Auflistungen werden unterstützt. Headerdatei: <memory>. Weitere Informationen finden Sie unter Gewusst wie: 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 Gewusst wie: 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 Gewusst wie: 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.

  • Intelligente Zeiger der ATL für POCO-Objekte
    Zusätzlich zu den intelligenten Zeigern für COM-Objekte definiert ATL auch intelligente Zeiger sowie Auflistungen von intelligenten Zeigern für einfache alte C++-Objekte. In der klassischen Windows-Programmierung sind diese Typen nützliche Alternativen zu den STL-Auflistungen, insbesondere wenn Codeportabilität nicht erforderlich ist, oder wenn Sie die Programmiermodelle von STL und ATL nicht kombinieren möchten.

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

    • CHeapPtr Class
      Intelligenter Zeiger für Objekte, die mit der C-Funktion malloc zugeordnet werden.

    • CAutoVectorPtr Class
      Intelligenter Zeiger für Arrays, die mit new[] zugeordnet werden.

    • CAutoPtrArray Class
      Klasse, die ein Array mit CAutoPtr-Elementen kapselt.

    • CAutoPtrList Class
      Klasse, die Methoden zum Bearbeiten einer Liste von CAutoPtr-Knoten kapselt.

Siehe auch

Weitere Ressourcen

Willkommen zurück bei C++ (Modern C++)

C++-Sprachreferenz

C++-Standardbibliotheksreferenz

Übersicht: Speicherverwaltung in C++