Notre page Facebook | |
Notre groupe Facebook |
|_Accueil Langage C++
|_RAII et les « smart pointers »
|_Mise en oeuvre d'une classe de « smart pointers »
|_Utilisation de la classe std::unique_ptr
|_Utilisation de la classe std::shared_ptr
|_Gestion des cycles de « smart pointers »
|_Application de RAII à la gestion des Mutex
Accès rapide :
Présentation de la classe
Exemple d'utilisation 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.
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 |
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 |
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; } |
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; } |
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 !.
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 :