Udostępnij za pośrednictwem


Instrukcje: tworzenie wystąpień shared_ptr i korzystanie z nich

Typ shared_ptr jest inteligentnym wskaźnikiem w standardowej bibliotece języka C++, która jest przeznaczona dla scenariuszy, w których więcej niż jeden właściciel musi zarządzać okresem istnienia obiektu. Po zainicjowaniu wskaźnika shared_ptr można go kopiować, przekazywać wg wartości w argumentach funkcji oraz przypisywać do innych wystąpień wskaźnika shared_ptr. Wszystkie wystąpienia wskazują ten sam obiekt oraz mają wspólny dostęp do jednego „bloku sterującego”, który zwiększa i zmniejsza liczbę odwołań po każdym dodaniu nowego wskaźnika shared_ptr, wykroczeniu przez wskaźnik poza zakres lub jego zresetowaniu. Gdy licznik odwołań osiągnie zero, blok sterujący usuwa zasób pamięci i samego siebie.

Na ilustracji poniżej widać kilka wystąpień wskaźnika shared_ptr, które wskazują jedną lokalizację w pamięci.

Diagram przedstawiający dwa wystąpienia shared_ptr wskazujące jedną lokalizację pamięci.

Pierwszy diagram przedstawia wskaźnik współużytkowany P1, który wskazuje wystąpienie MyClass, a także blok kontrolny z liczbą ref = 1. Drugi diagram przedstawia dodanie innego współużytkowanego wskaźnika P2, który wskazuje również wystąpienie MyClass i wspólny blok sterowania, który ma teraz liczbę ref 2.

Przykład konfiguracji

W poniższych przykładach przyjęto założenie, że dołączono wymagane nagłówki i zadeklarowaliśmy wymagane typy, jak pokazano poniżej:

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

Przykład 1

Jeśli to możliwe, użyj funkcji make_shared , aby utworzyć shared_ptr element podczas tworzenia zasobu pamięci po raz pierwszy. Funkcja make_shared jest bezpieczna pod względem wyjątków. Używa tego samego wywołania, aby przydzielić pamięć do bloku kontrolnego i zasobu, co zmniejsza obciążenie związane z konstrukcją. Jeśli nie używasz make_sharedmetody , musisz użyć wyrażenia jawnego new do utworzenia obiektu przed przekazaniem go do konstruktora shared_ptr . Poniższy przykład pokazuje różne sposoby deklarowania i inicjowania wskaźnika shared_ptr razem z nowym obiektem.


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

Przykład 2

W poniższym przykładzie pokazano, jak zadeklarować i zainicjować shared_ptr wystąpienia, które przejmują współwłaścicieli obiektu przydzielonego przez inny shared_ptrobiekt . Załóżmy, że zainicjowanym wskaźnikiem sp2 jest 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);

Przykład 3

shared_ptr Jest również pomocny w kontenerach biblioteki standardowej języka C++ w przypadku używania algorytmów, które kopiują elementy. We wskaźniku shared_ptr można opakować elementy, po czym skopiować go do innych kontenerów przy założeniu, że bazowa pamięć jest zajęta tylko przez niezbędny czas, nie dłużej. Poniższy przykład pokazuje, jak używać algorytmu remove_copy_if do wystąpień wskaźnika shared_ptr w wektorze.

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

Przykład 4

Za pomocą funkcji dynamic_pointer_cast, static_pointer_cast i const_pointer_cast można rzutować wskaźnik shared_ptr. Funkcje te przypominają operatory dynamic_cast, static_cast i const_cast. W poniższym przykładzie pokazano, jak przetestować typ pochodny każdego elementu w wektorze shared_ptr klas bazowych, a następnie skopiować elementy i wyświetlić informacje 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;
}

Przykład 5

Wskaźnik shared_ptr można przekazać do innej funkcji w następujące sposoby:

  • Przekazanie wskaźnika shared_ptr wg wartości. Powoduje to wywołanie konstruktora kopiującego, zwiększenie wartości licznika odwołań i przekazanie własności obiektowi wywoływanemu. W tej operacji występuje niewielka ilość narzutów, co może być istotne w zależności od liczby shared_ptr przekazywanych obiektów. Użyj tej opcji, gdy dorozumiany lub jawny kontrakt kodu między obiektem wywołującym i wywoływanym wymaga, aby obiekt wywoływany był właścicielem.

  • Przekazanie wskaźnika shared_ptr wg odwołania lub odwołania stałego. W takim przypadku liczba odwołań nie jest zwiększana, a obiekt wywoływany może uzyskać dostęp do wskaźnika, o ile obiekt wywołujący nie wykracza poza zakres. Lub obiekt wywoływany może zdecydować się na utworzenie shared_ptr obiektu na podstawie odwołania i zostać właścicielem udostępnionym. Opcji należy używać w sytuacjach, gdy obiekt wywołujący nic nie wie o obiekcie wywoływanym albo kiedy trzeba przekazać wskaźnik shared_ptr, ale ze względów wydajnościowych operacje kopiowania są niepożądane.

  • Przekazanie bazowego wskaźnika lub odwołania do bazowego obiektu. Dzięki temu obiekt wywoływany może używać obiektu, ale nie umożliwia mu współużytkowania własności ani wydłużenia okresu istnienia. Jeśli obiekt wywoływany tworzy element shared_ptr z pierwotnego wskaźnika, nowy shared_ptr element jest niezależny od oryginalnego elementu i nie kontroluje bazowego zasobu. Opcję należy stosować w przypadku, gdy kontrakt między obiektami wywołującym i wywoływanym jednoznacznie określa, że obiekt wywołujący zachowuje własność nad okresem istnienia wskaźnika shared_ptr.

  • Podczas podejmowania decyzji, jak przekazać element shared_ptr, określ, czy obiekt wywoływany musi współużytkować własność bazowego zasobu. „Właściciel” to obiekt lub funkcja, która może utrzymywać istnienie bazowego zasobu tak długo, jak to konieczne. Jeśli obiekt wywołujący ma gwarantować, że obiekt wywoływany może przedłużać okres istnienia wskaźnika poza okres istnienia funkcji, należy użyć pierwszej opcji. Gdy przedłużanie okresu istnienia przez obiekt wywoływany nie ma znaczenia, należy stosować przekazywanie wg odwołania i pozwolić obiektowi wywoływanemu na opcjonalne kopiowanie wskaźnika.

  • Jeśli musisz udzielić funkcji pomocniczej dostępu do bazowego wskaźnika i wiesz, że funkcja pomocnika używa wskaźnika i zwraca przed zwróceniem funkcji wywołującej, ta funkcja nie musi współdzielić własności bazowego wskaźnika. Potrzebuje tylko dostępu do wskaźnika w trakcie okresu istnienia obiektu wywołującego wskaźnika shared_ptr. W takim przypadku można bezpiecznie przekazać shared_ptr odwołanie lub przekazać pierwotny wskaźnik lub odwołanie do obiektu bazowego. Taki sposób przekazania jest nieco korzystniejszy pod względem obciążenia systemu, a dodatkowo może pomóc lepiej wyrazić cele programistyczne.

  • Czasami, na przykład w konstrukcji std::vector<shared_ptr<T>>, może być konieczne przekazanie każdego wskaźnika shared_ptr do treści wyrażenia lambda lub do nazwanego obiektu funkcji. Jeśli funkcja lambda lub nie przechowuje wskaźnika, przekaż shared_ptr odwołanie, aby uniknąć wywoływania konstruktora kopiowania dla każdego elementu.

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

Przykład 6

W przykładzie poniżej pokazano, jak wskaźnik shared_ptr przeciąża różne operatory porównania, aby umożliwić porównywanie wskaźników w pamięci, której właścicielami są wystąpienia wskaźnika 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; 

Zobacz też

Wskaźniki inteligentne (Modern C++)