Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Notre page Facebook
Notre groupe Facebook


Android Game
Alien Attacks - Android Game

Utilisation de la classe std::shared_ptr

Accès rapide :
      Présentation de la classe
      Exemple d'utilisation de la classe

Présentation de la classe

Les classes de pointeurs malins (smart pointers) fournies en standard dans la librairie C++ sont accessible via l'entête <memory>. C'est notamment le cas de la classe std::shared_ptr. Il est néanmoins à noter que cette classe est disponible depuis la version 2011 du standard C++ (l'option -std=c++11 est donc requise pour compiler avec g++).

La classe std::unique_ptr respecte le principe RAII (Resource Acquisition Is Initialization - voir la page d'introduction à ce chapitre à ce sujet) en garantissant que la durée de vie de votre pointeur et de la zone de mémoire associée sont liés à la durée de vie de l'instance de pointeur malin. En cas de copies de std::shared_ptr, des compteurs seront mis en place pour garantir de la resource sera bien relâchée lors de la perte du dernier pointeur malin.

Remarque : cette classe provient du projet Boost. Cette information est importante pour ceux qui souhaiteraient utiliser cette classe avec une chaine de compilation antérieur au standard C++11.

Exemple d'utilisation de la classe

Histoire de tester sérieusement le concept, nous allons l'utiliser au travers d'une « mini implementation » d'arbre binaire. Un arbre binaire est une structure de données récursive qui permet d'organiser les valeurs sous forme d'une arborescence de noeuds en mémoire. Traditionnellement, les données inférieures sont stockées dans le sous-arbre de gauche tandis que les données supérieures sont stockées dans le sous-arbre de droite. Voici un exemple d'implémentation (seul l'ajout de données est ici pris en charge).

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
#ifndef __TREE
#define __TREE

#include <memory>


template <typename ELEMENT>
class TreeNode {
    typedef std::shared_ptr<TreeNode<ELEMENT>> TreeNodePtr;

    TreeNodePtr leftNode;
    ELEMENT value;
    TreeNodePtr rightNode;
public:
    TreeNode(const ELEMENT & value) : leftNode(), value(value), rightNode() {}
    virtual ~TreeNode();

    void push(TreeNodePtr node);
};


template <typename ELEMENT>
class Tree {
    typedef std::shared_ptr<TreeNode<ELEMENT>> TreeNodePtr;

    TreeNodePtr root;
public:
    Tree() : root() {}
    ~Tree();

    void push(const ELEMENT & value);
};

////////////////////////////////////////////////////////
/// Implementations
////////////////////////////////////////////////////////

template <typename ELEMENT>
TreeNode<ELEMENT>::~TreeNode() {
    std::cout << "Node " << this->value << " released" << std::endl;
}

template <typename ELEMENT>
void TreeNode<ELEMENT>::push( TreeNodePtr node ) {
    if (node->value < this->value) {
        if (this->leftNode.get() == nullptr) {
            this->leftNode = node;
            std::cout << "Insert on left of " << this->value << std::endl;
        } else {
            this->leftNode->push(node);
        }
    } else {
        if (this->rightNode.get() == nullptr) {
            this->rightNode = node;
            std::cout << "Insert on right of " << this->value << std::endl;
        }
        else {
            this->rightNode->push(node);
        }
    }
}


template <typename ELEMENT>
Tree<ELEMENT>::~Tree() {
    std::cout << "Tree released" << std::endl;
}

template <typename ELEMENT>
void Tree<ELEMENT>::push(const ELEMENT & value) {
    TreeNodePtr node(new TreeNode<ELEMENT>(value));
    if (root.get() == nullptr) {
        this->root = node;
        std::cout << "First insertion of " << value << std::endl;
    } else {
        this->root->push(node);
    }
}

#endif
Tree.h : classe template d'arbre binaire à base de std::shared_ptr.

Nous allons reprendre l'exemple de classe de ressources vue dans les pages précédentes en lui rajoutant la capacité de comparer deux instances de ressources (afin de les ranger dans l'arbre) ainsi qu'un typedef basé sur le type std::shared_ptr.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
#ifndef __RESOURCE
#define __RESOURCE

#include <memory>
#include <string>


class Resource {
    std::string name;

public:
    Resource( const std::string & name );
    ~Resource();

    void doSomething();

    bool operator <( const Resource & other ) const;

};

typedef std::shared_ptr< Resource > ResourcePtr;

#endif
Resource.h : déclaration de notre classe qui va être prise en charge par nos pointeurs malins

Voici le fichier d'implémentation de notre classe.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
#include <iostream>

using namespace std;

#include "Resource.h"


Resource::Resource( const std::string & name ) : name( name ) {
    cout << "Resource " << name << " created" << endl;
}


Resource::~Resource() {
    cout << "Resource " << name << " released" << endl;
}


void Resource::doSomething() {
    cout << "Resource " << name << " used" << endl;
}


bool Resource::operator <( const Resource & other ) const {
    return this->name < other.name;
}
Resource.cpp : implémentation de notre classe de ressources

Et voici maintenant un exemple d'utilisation ou plusieurs instances de ressources sont stockées dans un arbre binaire. L'arbre est lui-même est instancié en dynamique, mais aussi les instances de noeuds et les ressources. Tous est maintenu par des std::shared_ptr.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
#include <iostream>

using namespace std;

#include "Tree.h"
#include "Resource.h"


int main() {

    ResourcePtr first( new Resource( "First value" ) );
    ResourcePtr second( new Resource( "Second value" ) );
    ResourcePtr third( new Resource( "Third value" ) );
    ResourcePtr fourth( new Resource( "Fourth value" ) );


    typedef std::shared_ptr< Tree<ResourcePtr> >   TreePtr;
    TreePtr tree( new Tree<ResourcePtr>() );
    tree->push( first );
    tree->push( second );
    tree->push( third );
    tree->push( fourth );

    return 0;
}
main.cpp : un exemple d'utilisation de std::shared_ptr

Compilons maintenant notre programme et lançons-le.

$> g++ -std=c++14 *.cpp -o raii
$> raii
Instance First value created
Instance Second value created
Instance Third value created
Instance Fourth value created
First insertion of 0x167bc20
Insert on right of 0x167bc20
Insert on right of 0x167bc70
Insert on right of 0x167bcc0
Tree released
Node 0x167bc20 released
Node 0x167bc70 released
Node 0x167bcc0 released
Node 0x167bd10 released
Instance Fourth value released
Instance Third value released
Instance Second value released
Instance First value released
$>

Dès que le programme se termine, l'ensemble des ressources allouées en dynamique sont automatiquement relâchées : vive RAII et les pointeurs malins !.


Utilisation de la classe std::unique_ptr <<
>> Gestion des cycles de « smart pointers »