Guide pratique : Créer et utiliser des instances de shared_ptr

Le type shared_ptr est un pointeur intelligent de la bibliothèque standard C++ conçu pour des scénarios où plusieurs propriétaires peuvent devoir gérer la durée de vie d’un objet. Après avoir initialisé shared_ptr, vous pouvez le copier, le passer par valeur dans des arguments de fonction et l'assigner à d'autres instances shared_ptr. Toutes les instances pointent vers le même objet et partagent l'accès à un "bloc de contrôle" qui incrémente et décrémente le nombre de références chaque fois qu'un nouvel shared_ptr est ajouté, est hors de portée ou est réinitialisé. Lorsque le nombre de références atteint zéro, le bloc de contrôle supprime la ressource mémoire et lui-même.

L'illustration suivante représente plusieurs instances shared_ptr qui pointent vers un emplacement de mémoire.

Diagramme montrant deux instances de shared_ptr pointant vers un même emplacement mémoire.

Le premier diagramme montre un pointeur partagé, P1, qui pointe vers une instance de MyClass ainsi qu’un bloc de contrôle avec Ref count (nombre de références) = 1. Le deuxième diagramme montre l’ajout d’un autre pointeur partagé, P2, qui pointe également vers l’instance de MyClass et vers le bloc de contrôle partagé, qui a maintenant un Ref count (nombre de références) = 2.

Exemple de configuration

Tous les exemples qui suivent supposent que vous avez inclus les en-têtes nécessaires et déclaré les types nécessaires, comme illustré ici :

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

Exemple 1

Quand c’est possible, utilisez la fonction make_shared pour créer un objet shared_ptr quand la ressource mémoire est créée pour la première fois. make_shared est protégé contre les exceptions. Elle utilise le même appel pour allouer de la mémoire pour le bloc de contrôle et pour la ressource, ce qui réduit la charge liées à la construction. Si vous n’utilisez pas make_shared, vous devez utiliser une expression new explicite pour créer l’objet avant de le passer au constructeur shared_ptr. L'exemple suivant indique différentes façons de déclarer et d'initialiser shared_ptr avec un nouvel objet.


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

Exemple 2

L’exemple suivant montre comment déclarer et initialiser les instances de shared_ptr qui prennent en charge la propriété partagée d’un objet qui a été alloué par un autre shared_ptr. Supposons que sp2 est un shared_ptr initialisé.

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

Exemple 3

shared_ptr est également utile dans les conteneurs de la bibliothèque standard C++ quand vous utilisez des algorithmes qui copient des éléments. Vous pouvez encapsuler des éléments dans un shared_ptr, puis le copier dans d'autres conteneurs à condition que la mémoire sous-jacente soit valide tant que cela est nécessaire, et pas plus longtemps. L'exemple suivant montre comment utiliser les algorithmes remove_copy_if sur des instances shared_ptr au sein d'un vecteur.

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

Exemple 4

Utilisez dynamic_pointer_cast, static_pointer_cast et const_pointer_cast, pour caster un shared_ptr. Ces fonctions sont semblables aux opérateurs dynamic_cast, static_cast et const_cast. L’exemple suivant montre comment tester le type dérivé de chaque élément dans un vecteur de shared_ptr de classes de base, puis copier les éléments et afficher les informations sur ceux-ci.

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

Exemple 5

Vous pouvez passer shared_ptr à une autre fonction des façons suivantes :

  • Passez shared_ptr par valeur. Il appelle le constructeur de copie, incrémente le nombre de références et fait de l'appelé un propriétaire. Cette opération induit une petite surcharge, qui peut être significative selon le nombre d’objets shared_ptr que vous passez. Utilisez cette option quand le contrat de code implicite ou explicite entre l’appelant et l’appelé nécessite que l’appelé soit un propriétaire.

  • Passez shared_ptr par référence ou référence const. Dans ce cas, le nombre de références n’est pas incrémenté, et l’appelé peut accéder au pointeur tant que l’appelant n’est pas en dehors de l’étendue. Sinon, l’appelé peut décider de créer un shared_ptr basé sur la référence, et de devenir un propriétaire partagé. Utilisez cette option lorsque l'appelant n'a pas connaissance de l'appelé, ou lorsque vous devez passer un shared_ptr et que vous voulez empêcher l'opération de copie pour des raisons de performance.

  • Passez le pointeur ou une référence sous-jacent(e) à l'objet sous-jacent. Ceci permet à l’appelé d’utiliser l’objet, mais ne lui permet pas de partager la propriété ou d’étendre la durée de vie. Si l’appelé crée un shared_ptr à partir du pointeur brut, le nouveau shared_ptr est indépendant de l’original et ne contrôle pas la ressource sous-jacente. Utilisez cette option lorsque le contrat entre l'appelant et l'appelé spécifie clairement que l'appelant conserve la propriété de la durée de vie de shared_ptr.

  • Quand vous décidez comment passer un shared_ptr, déterminez si l’appelé doit partager la propriété de la ressource sous-jacente. Un "propriétaire" est un objet ou une fonction qui peut garder la ressource sous-jacente active tant qu'il en a besoin. Si l'appelant doit garantir que l'appelé peut prolonger la vie du pointeur au delà de la durée de vie de la fonction, utilisez le première option. Si vous ne vous préoccupez pas de si l'appelé étend la durée de vie ou non, alors effectuez une transmission par référence et laissez l'appelé la copier ou non.

  • Si vous devez donner à une fonction d’assistance un accès au pointeur sous-jacent, et que vous savez que cette fonction d’assistance utilise le pointeur et retourne avant que la fonction appelante ne retourne, cette fonction n’a pas à partager la propriété du pointeur sous-jacent. Elle doit simplement accéder au pointeur dans la durée de vie du shared_ptrde l'appelant. Dans ce cas, cela ne pose pas de problème de passer le shared_ptr par référence, ou de passer le pointeur brut ou une référence à l’objet sous-jacent. Passer de cette façon fournit un léger avantage en termes de performances et peut également vous aider à exprimer votre intention de programmation.

  • Parfois, par exemple dans un std::vector<shared_ptr<T>>, vous devrez peut-être passer chaque shared_ptr à une expression Lambda ou un objet fonction nommé. Si l’expression lambda ou la fonction ne stocke pas le pointeur, passez le shared_ptr par référence pour éviter d’appeler le constructeur de copie pour chaque élément.

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

Exemple 6

L'exemple suivant montre comment shared_ptr surcharge différents opérateurs de comparaison pour activer des comparaisons de pointeur dans la mémoire qui est possédée par les instances 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; 

Voir aussi

Pointeurs intelligents (C++ moderne)