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


Mise en oeuvre d'une classe de « smart pointers »

Accès rapide :
      Définition de la classe générique de pointeurs malins
      Codage de votre classe de ressources
      Utilisation des pointeurs malins pour la gestion de vos ressources
      Cas des « factories »
      Constructeurs explicites

Définition de la classe générique de pointeurs malins

Comme expliqué dans la section précédente, nous allons maintenant chercher à coder notre propre classe de pointeurs malins (smart pointers). Nous sommes bien d'accord : normalement, nous ne devrions pas faire cela étant donné que le standard C++ 2011 fourni déjà plusieurs types d'implémentations de pointeurs malins. Nous allons néanmoins procéder à cet exercice uniquement dans un but purement pédagogique. Sur les pages suivantes, il sera bien entendu préférable d'utiliser les classes standards.

Une classe de pointeurs malins doit donc permettre de manipuler des objets alloués dynamiquement (via l'opérateur new) de nature quelconque. Il en découle qu'il est donc judicieux de passer par un template pour mettre en oeuvre une telle classe, afin de pouvoir profiter de la généricité. Ensuite, une classe de pointeurs malins doit contenir un pointeur vers la ressource allouée dynamiquement. Elle devra de plus permettre de partager un compteur de pointeurs sur la ressource et ce compteur sera partagé par tous les pointeurs malins liés à cette ressource (nous allons pouvoir dupliquer les pointeurs malins). Notre classe template devra donc contenir deux attributs.

Ensuite, deux comportements majeurs devront être gérés par votre classe de pointeurs malins :

Voici une proposition de codage pour une telle classe template.

 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 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
 96 
 97 
#ifndef __SHARED_PTR
#define __SHARED_PTR

///////////////////////////////////////////////////////////
// Templated class declaration
///////////////////////////////////////////////////////////

template <typename RESOURCE>
class SharedPtr {

    RESOURCE * rawPointer;
    unsigned int * pCounter;

    void decrease();
public:
    inline SharedPtr(const RESOURCE * rawPointer = nullptr);
    inline SharedPtr(const SharedPtr<RESOURCE> & original);
    inline ~SharedPtr();

    inline SharedPtr<RESOURCE> & operator=(const SharedPtr<RESOURCE> & original);

    inline void reset();

    inline RESOURCE * get() const;
    inline RESOURCE * operator->() const;
    inline operator RESOURCE *() const;
    inline RESOURCE & operator*() const;
};

///////////////////////////////////////////////////////////
// Method implementations
///////////////////////////////////////////////////////////

template <typename RESOURCE>
SharedPtr<RESOURCE>::SharedPtr(const RESOURCE * rawPointer) :
rawPointer(const_cast<RESOURCE *>(rawPointer)), pCounter(new unsigned int(1)) {
}

template <typename RESOURCE>
SharedPtr<RESOURCE>::SharedPtr(const SharedPtr<RESOURCE> & original) {
    this->rawPointer = original.rawPointer;
    this->pCounter = original.pCounter;
    (*pCounter)++;
}

template <typename RESOURCE>
void SharedPtr<RESOURCE>::decrease() {
    if (--(*this->pCounter) == 0) {
        delete this->pCounter;
        delete this->rawPointer;
    }
}

template <typename RESOURCE>
SharedPtr<RESOURCE>::~SharedPtr() {
    this->decrease();
}

template <typename RESOURCE>
SharedPtr<RESOURCE> & SharedPtr<RESOURCE>::operator=(const SharedPtr<RESOURCE> & original) {
    if (this->rawPointer != original.rawPointer) {
        this->decrease();
        this->rawPointer = original.rawPointer;
        this->pCounter = original.pCounter;
        (*pCounter)++;
    }
    return *this;
}

template <typename RESOURCE>
void SharedPtr<RESOURCE>::reset() {
    this->decrease();
    this->rawPointer = nullptr;
    (*this->pCounter) = 1;
}

template <typename RESOURCE>
RESOURCE * SharedPtr<RESOURCE>::operator ->() const {
    return this->rawPointer;
}

template <typename RESOURCE>
RESOURCE * SharedPtr<RESOURCE>::get() const {
    return this->rawPointer;
}

template <typename RESOURCE>
SharedPtr<RESOURCE>::operator RESOURCE *() const {
    return this->rawPointer;
}

template <typename RESOURCE>
RESOURCE & SharedPtr<RESOURCE>::operator *() const {
    return *this->rawPointer;
}

#endif
SharedPtr.h : définition d'une classe template de pointeurs malins

Codage de votre classe de ressources

Afin de comprendre l'exécution de notre programme nous allons créer une classe dont les instances seront prises en charge par nos pointeurs malins. Nous allons définir dans cette classe trois méthodes afin d'avoir un mini cycle de vie sur ces instances. A chaque point de ce cycle de vie, nous allons produire un affichage sur la console. Parmi ces trois méthodes nous aurons, bien entendu, le destructeur de notre classe et nous apporterons une attention toute particulière au déclenchement automatique de cette méthode. Voici une proposition de déclaration de cette classe.

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

#include <string>

#include "SharedPtr.h"


class Resource {
    std::string name;

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

    void doSomething();
};

typedef SharedPtr< Resource > ResourcePtr;

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

Et voici maintenant 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 
#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;
}
Resource.cpp : implémentation de notre classe

Notez la définition du typedef afin de simplifier l'utilisation de nos pointeurs malins.

Utilisation des pointeurs malins pour la gestion de vos ressources

Nous sommes maintenant près à faire nos premiers tests sur l'utilisation de nos pointeurs malins. Voici un premier exemple basique. Le pointeur malin est construit à partir du pointeur retourné par l'opérateur new. Ensuite la perte de l'instance de pointeur malin entraine la libération automatique de la ressource.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
#include <iostream>

using namespace std;

#include "Resource.h"


int main(int argc, char * argv[]) {

    ResourcePtr ptr1 = new Resource( "Demo" );
    ptr1->doSomething();

    return 0;
}
UseResource.cpp : utilisation d'une instance de pointeur malin.

Compilons maintenant notre programme et lançons-le.

$> g++ -std=c++14 *.cpp -o raii
$> raii
Resource Demo created
Resource Demo used
Resource Demo released
$>

Cas des « factories »

Voici un exemple de fonction qu'on peut retrouver dans la librairie POSIX (Portable Operating System Interface X). Question : pourquoi ce type de fonction ne se retrouve pas dans la librairie standard C ?

 1 
 2 
 3 
 4 
 5 
char * strdup(const char * original) {
    size_t length = strlen(original) + 1;
    char * buffer = new char[length];
    return strcpy(buffer, original);
}
Exemple de codage d'une fonction strdup (STRing DUPlication)

La raison en est simple, c'est une histoire de responsabilité. Dans ce genre de fonction, la responsabilité d'allouer une ressource est donnée à la fonction alors que la responsabilité de libérer la ressource est réservée à l'utilisateur de la fonction strdup. Résultat : souvent les développeurs ne sont pas conscients qu'ils ont cette responsabilité de libérer la ressource. En conséquence, certains développeurs C et C++ s'interdisent de coder ce type de fonctions/méthodes qui allouent des ressources.

Avec RAII, on peut se réconcilier avec ce genre de code, étant donné que la responsabilité de libérer la ressource n'est plus donnée à l'utilisateur de la librairie, mais, on contraire, exécutée automatiquement. On peut donc, par exemple, plus facilement mettre en oeuvre les designs patterns de construction (factory method, abstract factory, singleton, builder, ...).

Reprenons notre exemple précédent et testons ce type de fonctions : vous verrez qu'en fin d'exécution du programme, la ressource sera bien relâchée.

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

using namespace std;

#include "Resource.h"


ResourcePtr aFactoryMethod() {
    ResourcePtr temp = new Resource( "Demo" );
    temp->doSomething();
    return temp;
}


int main(int argc, char * argv[]) {

    ResourcePtr ptr1 = aFactoryMethod();
    ptr1->doSomething();

    return 0;
}
UseResource.cpp : transfert d'un pointeur malin. d'un contexte à un autre

Pour lancer ce programme :

$> g++ -std=c++14 *.cpp -o raii
$> raii
Resource Demo created
Resource Demo used
Resource Demo used
Resource Demo released
$>

Constructeurs explicites


Comme nous allons le voir dans les pages suivantes, des classes de pointeurs malins (avec diverses stratégies) sont déjà proposées en standards (du moins avec C++ ISO 2011). Une des différences entre notre classe et celles proposées en standard réside dans le fait que les constructeurs des classes standards seront marqués comme étant à invocation explicites. Modifions notre constructeur en conséquence pour se rapprocher des classes standards.

 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 
#ifndef __SHARED_PTR
#define __SHARED_PTR

///////////////////////////////////////////////////////////
// Templated class declaration
///////////////////////////////////////////////////////////

template <typename RESOURCE>
class SharedPtr {

    RESOURCE * rawPointer;
    unsigned int * pCounter;

    void decrease();
public:
    explicit SharedPtr(const RESOURCE * rawPointer = nullptr);
    SharedPtr(const SharedPtr<RESOURCE> & original);
    ~SharedPtr();

    SharedPtr<RESOURCE> & operator=(const SharedPtr<RESOURCE> & original);

    RESOURCE * get() const;
    RESOURCE * operator->() const;
    operator RESOURCE *() const;
    RESOURCE & operator*() const;
};

///////////////////////////////////////////////////////////
// Method implementations
///////////////////////////////////////////////////////////

// ...
// Suite du code inchangé par rapport à l'exemple initial ...
// ...

#endif
SharedPtr.h : implémentation de notre pointeur malin avec son constructeur explicite

Et maintenant, revoici notre exemple d'utilisation d'un pointeur malin avec l'utilisation du constructeur à invocation explicite.

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

using namespace std;

#include "Resource.h"


ResourcePtr aFactoryMethod() {
    ResourcePtr temp( new Resource( "Demo" ) );    // explicit constructor
    temp->doSomething();
    return temp;
}


int main(int argc, char * argv[]) {

    ResourcePtr ptr1 = aFactoryMethod();
    ptr1->doSomething();

    return 0;
}
UseResource.cpp : invocation explicite du constructeur

Si le principe est bien compris, nous pouvons maintenant passer à l'étude des classes standards C++ ISO 2011.


RAII et les « smart pointers » <<
>> Utilisation de la classe std::unique_ptr