Sdílet prostřednictvím


Postupy: Vytváření a používání instancí shared_ptr

Typ shared_ptr je inteligentní ukazatel ve standardní knihovně jazyka C++, který je určený pro scénáře, ve kterých musí životnost objektu spravovat více než jeden vlastník. Po inicializaci typu shared_ptr jej lze zkopírovat, předat hodnotou argumentům funkce nebo přiřadit dalším instancím typu shared_ptr. Všechny tyto instance ukazují na stejný objekt a sdílejí přístup k jednomu „řídicímu bloku“, který zvyšuje a snižuje počet odkazů, kdykoli je nová instance typu shared_ptr přidána, dostane se mimo rozsah nebo je obnovena. Když počet odkazů dosáhne nuly, řídicí blok odstraní prostředky paměti a sám sebe.

Následující obrázek znázorňuje několik instancí typu shared_ptr, které odkazují na jedno umístění v paměti.

Diagram znázorňující dvě instance shared_ptr ukazující na jedno umístění paměti

První diagram znázorňuje sdílený ukazatel P1, který odkazuje na instanci MyClass a řídicí blok s počtem odkazů = 1. Druhý diagram znázorňuje přidání dalšího sdíleného ukazatele P2, který také odkazuje na instanci MyClass a na sdílený řídicí blok, který má nyní počet odkazů 2.

Příklad nastavení

Následující příklady předpokládají, že jste zahrnuli požadované hlavičky a deklarovali požadované typy, jak je znázorněno tady:

// 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
}

Příklad 1

Kdykoli je to možné, použijte funkci make_shared k vytvoření shared_ptr prvního prostředku paměti. Funkce make_shared zaručuje bezpečnost výjimek. Používá stejné volání k přidělení paměti pro řídicí blok a prostředek, což snižuje režijní náklady na výstavbu. Pokud nepoužíváte make_shared, musíte před předáním objektu shared_ptr konstruktoru použít explicitní new výraz. Následující příklad ukazuje různé způsoby deklarace a inicializace instancí typu shared_ptr společně s novým objektem.


// 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");

Příklad 2

Následující příklad ukazuje, jak deklarovat a inicializovat shared_ptr instance, které převzít sdílené vlastnictví objektu, který byl přidělen jiným shared_ptr. Předpokládejme, že proměnná sp2 je inicializována instancí typu 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);

Příklad 3

shared_ptr je také užitečný v kontejnerech standardní knihovny C++ při použití algoritmů, které kopírují elementy. Prvky lze zabalit do instance typu shared_ptr a poté je zkopírovat do jiných kontejnerů s vědomím, že základní paměť je platná tak dlouho, dokud ji potřebujete a ne déle. Následující příklad ukazuje, jak použít algoritmus remove_copy_if s instancemi typu shared_ptr v instanci typu vector.

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;
}

Příklad 4

K přetypování instance typu dynamic_pointer_cast lze použít funkce static_pointer_cast, const_pointer_cast a shared_ptr. Tyto funkce se podobají operátorům dynamic_cast, static_cast a const_cast. Následující příklad ukazuje, jak otestovat odvozený typ každého prvku v vektoru shared_ptr základních tříd a pak zkopíruje prvky a zobrazí informace o nich.

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;
}

Příklad 5

Instanci typu shared_ptr lze předat jiné funkci následujícími způsoby:

  • Předání instance typu shared_ptr hodnotou. To zavolá kopii konstruktoru, zvýší počet odkazů a volaného učiní vlastníkem. V této operaci je menší režie, což může být významné v závislosti na tom, kolik shared_ptr objektů předáváte. Tuto možnost použijte, pokud implicitní nebo explicitní kontrakt kódu mezi volajícím a volaným vyžaduje, aby volaný byl vlastníkem.

  • Předání instance typu shared_ptr odkazem nebo konstantním odkazem. V tomto případě se počet odkazů nezvýší a volaný má přístup k ukazateli, pokud volající nevyjde z rozsahu. Nebo se volaný může rozhodnout vytvořit shared_ptr na základě odkazu a stát se sdíleným vlastníkem. Tuto možnost použijte, když volající nezná volaného nebo když je nutné předat instanci typu shared_ptr a chcete se vyhnout operaci kopírování z důvodů výkonu.

  • Předání základního ukazatele nebo odkazu na základní objekt. To umožňuje volaný objekt používat, ale neumožňuje mu sdílet vlastnictví ani prodloužit životnost. Pokud volaný vytvoří shared_ptr z nezpracovaného ukazatele, nový shared_ptr je nezávislý na původním objektu a neřídí podkladový prostředek. Tuto možnost použijte, pokud kontrakt mezi volajícím a volaným jasně určuje, že volajícímu zůstane vlastnictví po dobu života instance typu shared_ptr.

  • Při rozhodování o tom, jak předat shared_ptr, určete, jestli volaný musí sdílet vlastnictví základního prostředku. „Vlastník“ je objekt nebo funkce, která může základní prostředek zachovat naživu tak dlouho, dokud jej potřebuje. Pokud volající musí zaručit, že volaný může prodloužit dobu života ukazatele nad rámec své doby života (funkce), použijte první možnost. Pokud není důležité, zda volaný prodlouží dobu života, použijte předání odkazem a nechte volaného, aby je zkopíroval či nikoli.

  • Pokud potřebujete pomocnou funkci poskytnout přístup k podkladovému ukazateli a víte, že pomocná funkce použije ukazatel a vrátí se před vrácením volající funkce, nemusí tato funkce sdílet vlastnictví základního ukazatele. K tomuto ukazateli má přístup jen během doby života instance typu shared_ptr volajícího. V tomto případě je bezpečné předat shared_ptr odkaz nebo předat nezpracovaný ukazatel nebo odkaz na podkladový objekt. Předání tímto způsobem mírně zlepšuje výkon a může také pomoci lépe vyjádřit záměr programu.

  • Někdy, například pro typ std::vector<shared_ptr<T>>, bude pravděpodobně nutné předat každou instanci typu shared_ptr tělu výrazu lambda nebo objektu pojmenované funkce. Pokud lambda nebo funkce neukládá ukazatel, předejte shared_ptr odkaz, aby se zabránilo vyvolání konstruktoru kopírování pro každý prvek.

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));
}

Příklad 6

Následující příklad ukazuje, jak typ shared_ptr přetěžuje různé operátory porovnání, což umožňuje porovnání ukazatelů na paměť, která je vlastněna instancemi typu shared_ptr.


// 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; 

Viz také

Chytré ukazatele (moderní verze jazyka C++)