Vorgehensweise: Erstellen und Verwenden von „shared_ptr“-Instanzen

Der Typ shared_ptr ist ein intelligenter Zeiger in der C++-Standardbibliothek für Szenarien, in denen mehrere Besitzer die Lebensdauer eines Objekts verwalten müssen. Nachdem Sie einen shared_ptr initialisiert haben, können Sie ihn kopieren, als Wert an Funktionsargumente übergeben oder anderen shared_ptr-Instanzen zuweisen. Alle Instanzen zeigen auf dasselbe Objekt und greifen gemeinsam auf einen "Kontrollblock" zu, der den Verweiszähler erhöht bzw. verringert, wenn ein neuer shared_ptr hinzugefügt wird, den Gültigkeitsbereich verlässt oder zurückgesetzt wird. Wenn der Verweiszähler Null erreicht, löscht der Kontrollblock die Speicherressource und sich selbst.

Die folgende Abbildung zeigt mehrere shared_ptr-Instanzen, die auf einen Speicherbereich zeigen.

Diagramm mit zwei „shared_ptr“-Instanzen, die auf einen Speicherort verweisen.

Das erste Diagramm enthält den gemeinsamen Zeiger „P1“, der auf eine „MyClass“-Instanz zeigt, sowie einen Steuerblock mit „Ref count = 1“. Das zweite Diagramm enthält den zusätzlichen gemeinsamen Zeiger „P2“, der auch auf die „MyClass“-Instanz und den gemeinsamen Steuerblock zeigt, der jetzt „Ref count = 2“ aufweist.

Beispiel für die Einrichtung

Bei allen folgenden Beispielen wird davon ausgegangen, dass Sie die erforderlichen Header hinzugefügt und die erforderlichen Typen, wie hier gezeigt, deklariert haben:

// shared_ptr-examples.cpp
// The following examples assume these declarations:
#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

struct MediaAsset
{
    virtual ~MediaAsset() = default; // make it polymorphic
};

struct Song : public MediaAsset
{
    std::wstring artist;
    std::wstring title;
    Song(const std::wstring& artist_, const std::wstring& title_) :
        artist{ artist_ }, title{ title_ } {}
};

struct Photo : public MediaAsset
{
    std::wstring date;
    std::wstring location;
    std::wstring subject;
    Photo(
        const std::wstring& date_,
        const std::wstring& location_,
        const std::wstring& subject_) :
        date{ date_ }, location{ location_ }, subject{ subject_ } {}
};

using namespace std;

int main()
{
    // The examples go here, in order:
    // Example 1
    // Example 2
    // Example 3
    // Example 4
    // Example 6
}

Beispiel 1

Verwenden Sie nach Möglichkeit die Funktion make_shared zum Erstellen eines shared_ptr, wenn die Speicherressource zum ersten Mal erstellt wird. make_shared ist ausnahmesicher. Die Funktion verwendet den gleichen Aufruf zum Zuweisen des Arbeitsspeichers für den Kontrollblock und die Ressource, wodurch der Konstruktionsmehraufwand verringert wird. Wenn Sie make_shared nicht verwenden, müssen Sie einen expliziten new-Ausdruck zum Erstellen des Objekts verwenden, bevor Sie es an den shared_ptr-Konstruktor übergeben. Das folgende Beispiel zeigt verschiedene Möglichkeiten zum Deklarieren und Initialisieren eines shared_ptr zusammen mit einem neuen Objekt.


// Use make_shared function when possible.
auto sp1 = make_shared<Song>(L"The Beatles", L"Im Happy Just to Dance With You");

// Ok, but slightly less efficient. 
// Note: Using new expression as constructor argument
// creates no named variable for other code to access.
shared_ptr<Song> sp2(new Song(L"Lady Gaga", L"Just Dance"));

// When initialization must be separate from declaration, e.g. class members, 
// initialize with nullptr to make your programming intent explicit.
shared_ptr<Song> sp5(nullptr);
//Equivalent to: shared_ptr<Song> sp5;
//...
sp5 = make_shared<Song>(L"Elton John", L"I'm Still Standing");

Beispiel 2

Im folgenden Beispiel wird veranschaulicht, wie shared_ptr-Instanzen deklariert und initialisiert werden, die den gemeinsamen Besitz eines Objekts übernehmen, das von einem anderen shared_ptr zugeordnet wurde. Angenommen, sp2 ist ein initialisierter shared_ptr.

//Initialize with copy constructor. Increments ref count.
auto sp3(sp2);

//Initialize via assignment. Increments ref count.
auto sp4 = sp2;

//Initialize with nullptr. sp7 is empty.
shared_ptr<Song> sp7(nullptr);

// Initialize with another shared_ptr. sp1 and sp2
// swap pointers as well as ref counts.
sp1.swap(sp2);

Beispiel 3

shared_ptr ist auch in Containern der C++ Standardbibliothek hilfreich, wenn Sie die Algorithmen verwenden, die Elemente kopieren. Sie können Elemente in einem shared_ptr umschließen und sie dann in andere Container kopieren. Voraussetzung ist, dass der zugrunde liegende Arbeitsspeicher solange gültig ist, wie Sie ihn benötigen, und nicht länger. Im folgenden Beispiel wird die Verwendung des remove_copy_if-Algorithmus für shared_ptr-Instanzen in einem Vektor dargestellt.

vector<shared_ptr<Song>> v {
  make_shared<Song>(L"Bob Dylan", L"The Times They Are A Changing"),
  make_shared<Song>(L"Aretha Franklin", L"Bridge Over Troubled Water"),
  make_shared<Song>(L"Thalía", L"Entre El Mar y Una Estrella")
};

vector<shared_ptr<Song>> v2;
remove_copy_if(v.begin(), v.end(), back_inserter(v2), [] (shared_ptr<Song> s) 
{
    return s->artist.compare(L"Bob Dylan") == 0;
});

for (const auto& s : v2)
{
    wcout << s->artist << L":" << s->title << endl;
}

Beispiel 4

Sie können dynamic_pointer_cast, static_pointer_cast und const_pointer_cast zum Umwandeln eines shared_ptr verwenden. Diese Funktionen ähneln den dynamic_cast-, static_cast- und const_cast-Operatoren. Im folgenden Beispiel wird das Testen des abgeleiteten Typs jedes Elements in einem Vektor mit shared_ptr für Basisklassen sowie das Kopieren der Elemente und Anzeigen der zugehörigen Informationen gezeigt.

vector<shared_ptr<MediaAsset>> assets {
  make_shared<Song>(L"Himesh Reshammiya", L"Tera Surroor"),
  make_shared<Song>(L"Penaz Masani", L"Tu Dil De De"),
  make_shared<Photo>(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")
};

vector<shared_ptr<MediaAsset>> photos;

copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool
{
    // Use dynamic_pointer_cast to test whether
    // element is a shared_ptr<Photo>.
    shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
    return temp.get() != nullptr;
});

for (const auto&  p : photos)
{
    // We know that the photos vector contains only 
    // shared_ptr<Photo> objects, so use static_cast.
    wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location << endl;
}

Beispiel 5

Sie können einen shared_ptr folgendermaßen an eine andere Funktion übergeben:

  • Übergeben Sie den shared_ptr als Wert. Dies ruft den Kopierkonstruktor auf, erhöht den Verweiszähler und macht den Aufgerufenen zum Besitzer. Bei diesem Vorgang kommt es zu einem geringfügigen Mehraufwand, der abhängig von der Anzahl der zu übergebenden shared_ptr-Objekte bedeutsam sein kann. Verwenden Sie diese Option, wenn der implizite oder explizite Codevertrag zwischen dem Aufrufer und dem Aufgerufenen erfordert, dass der Aufgerufene ein Besitzer ist.

  • Übergeben Sie den shared_ptr als Verweis oder konstanter Verweis. In diesem Fall wird der Verweiszähler nicht erhöht, und der Aufgerufene kann auf den Zeiger zugreifen, solange der Aufrufer den Gültigkeitsbereich nicht verlässt. Der Aufgerufene hat auch die Möglichkeit, einen shared_ptr auf Basis des Verweises zu erstellen und zu einem freigegebenen Besitzer zu werden. Verwenden Sie diese Option, wenn der Aufrufer den Aufgerufenen nicht kennt oder wenn Sie einen shared_ptr übergeben müssen und den Kopiervorgang aus Leistungsgründen vermeiden möchten.

  • Übergeben Sie den zugrunde liegenden Zeiger oder einen Verweis auf das zugrunde liegende Objekt. Dadurch kann der Aufgerufene das Objekt verwenden, jedoch nicht den Besitz teilen oder die Lebensdauer erweitern. Wenn der Aufgerufene einen shared_ptr auf der Grundlage des Rohdatenzeigers erstellt, ist der neue shared_ptr unabhängig vom Original und steuert die zugrunde liegende Ressource nicht. Verwenden Sie diese Option, wenn der Vertrag zwischen dem Aufrufer und dem Aufgerufenen klar festgelegt ist, dass der Aufrufer Besitzer der shared_ptr-Lebensdauer bleibt.

  • Bei der Entscheidung, wie ein shared_ptr übergeben wird, bestimmen Sie, ob der Aufgerufene den Besitz der zugrunde liegenden Ressource teilen muss. Ein "Besitzer" ist ein Objekt oder eine Funktion, die die zugrunde liegende Ressource beibehalten kann, solange sie benötigt wird. Wenn der Aufrufer sicherstellen muss, dass der Aufgerufene die Lebensdauer des Zeigers über dessen Lebensdauer (bzw. die Lebensdauer der Funktion) hinaus verlängern kann, verwenden Sie die erste Option. Wenn es nicht relevant ist, ob der Aufgerufene die Lebensdauer verlängert, übergeben Sie "shared_ptr" als Verweis und überlassen Sie es dem Aufgerufenen, diesen zu kopieren oder nicht.

  • Wenn Sie einer Hilfsfunktion Zugriff auf den zugrunde liegenden Zeiger erteilen müssen und Sie wissen, dass die Hilfsfunktion den Zeiger verwendet und dann zurückgegeben wird, bevor die aufrufende Funktion zurückgegeben wird, muss die Funktion den Besitz des zugrunde liegenden Zeigers nicht teilen. Sie muss lediglich innerhalb der Lebensdauer des shared_ptr des Aufrufers auf den Zeiger zugreifen. In diesem Fall ist es sicher, den shared_ptr als Verweis zu übergeben bzw. den Rohdatenzeiger oder einen Verweis an das zugrunde liegende Objekt zu übergeben. Ein Übergeben auf diese Weise bietet einen leichten Leistungsvorteil und hilft Ihnen dabei, Ihre Programmierabsicht besser zum Ausdruck zu bringen.

  • Manchmal, beispielsweise in einem std::vector<shared_ptr<T>>, müssen Sie jeden shared_ptr an einen Text eines Lambda-Ausdrucks oder ein benanntes Funktionsobjekt übergeben. Wenn das Lambda oder die Funktion den Zeiger nicht speichert, übergeben Sie den shared_ptr als Verweis, damit der Kopierkonstruktor nicht für jedes Element aufgerufen wird.

void use_shared_ptr_by_value(shared_ptr<int> sp);

void use_shared_ptr_by_reference(shared_ptr<int>& sp);
void use_shared_ptr_by_const_reference(const shared_ptr<int>& sp);

void use_raw_pointer(int* p);
void use_reference(int& r);

void test() {
    auto sp = make_shared<int>(5);

    // Pass the shared_ptr by value.
    // This invokes the copy constructor, increments the reference count, and makes the callee an owner.
    use_shared_ptr_by_value(sp);

    // Pass the shared_ptr by reference or const reference.
    // In this case, the reference count isn't incremented.
    use_shared_ptr_by_reference(sp);
    use_shared_ptr_by_const_reference(sp);

    // Pass the underlying pointer or a reference to the underlying object.
    use_raw_pointer(sp.get());
    use_reference(*sp);

    // Pass the shared_ptr by value.
    // This invokes the move constructor, which doesn't increment the reference count
    // but in fact transfers ownership to the callee.
    use_shared_ptr_by_value(move(sp));
}

Beispiel 6

Das folgende Beispiel zeigt, wie der shared_ptr verschiedene Vergleichsoperatoren überlädt, um Zeigervergleiche für den Arbeitsspeicher zu ermöglichen, der im Besitz der shared_ptr-Instanzen ist.


// Initialize two separate raw pointers.
// Note that they contain the same values.
auto song1 = new Song(L"Village People", L"YMCA");
auto song2 = new Song(L"Village People", L"YMCA");

// Create two unrelated shared_ptrs.
shared_ptr<Song> p1(song1);    
shared_ptr<Song> p2(song2);

// Unrelated shared_ptrs are never equal.
wcout << "p1 < p2 = " << std::boolalpha << (p1 < p2) << endl;
wcout << "p1 == p2 = " << std::boolalpha <<(p1 == p2) << endl;

// Related shared_ptr instances are always equal.
shared_ptr<Song> p3(p2);
wcout << "p3 == p2 = " << std::boolalpha << (p3 == p2) << endl; 

Weitere Informationen

Intelligente Zeiger (Modern C++)