Les wrappers de fonctions en C++ avec std::function

Cet article illustre l’utilisation des wrappers de fonctions en c++, apparu avec C++11.

Lors d’un laboratoire effectué dans le cadre d’un cours à l’HEIG, il était demandé d’implémenter un dictionnaire et de pouvoir effectuer des opérations sur celui-ci comme retrouver un mot, insérer un nouveau mot ou en supprimer. Finalement, le but final était d’implémenter un correcteur orthographique en C++.

Dans le cas présent, le code a été édulcoré pour se concentrer uniquement sur les wrappers de fonctions à travers la classe DictionaryContainer qui représente un dictionnaire et les opérations qu’on peut effectuer sur celui-ci. Plutôt que de déterminer un conteneur fixe pour le dictionnaire comme vector, l’objectif était de laisser libre le choix à l’utilisateur de la classe de choisir le conteneur. C’est là qu’entre en matière les wrappers de fonctions.

Le code utilisé dans cet article est disponible sur mon github à l’adresse suivante : github.com/rya-sge/AD-cplusplus/template

Etape 1 - Classe conteneur

La classe DictionaryContainer est notre classe wrapper.

  • Le type de donnée contenu dans le dictionnaire est définit par le template T. On peut dès lors avoir aussi bien des entiers , des strings que des objets dans notre dictionnaire.
  • Une entrée dans le dictionnaire est représenté par le terme key.

Fonctions publiques

Les trois opérations que la classe va fournir publiquement sont :

  • contains pour savoir si la clé est présente dans le dictionnaire

    bool contains(const T &key) const
    
  • insert pour insérer la clé dans le dictionnaire

void insert(const T &key) const
  • erase pour supprimer la clé du dictionnaire

     void erase(const T &key) const
           
    

Ce qui donne :

public:
  
    bool contains(const T &key) const {  // A compléter // 
    }

    void insert(const T &key) const { // A compléter // 
    }
   
    void erase(constT &key) const {// A compléter // 
    }
};

Ces trois opérations vont appeler les fonctions correspondante du conteneur choisi. Par exemple, pour un conteneur vector, la fonction insert va appeler push_back. Le paragraphe suivant décrit cette implémentation

Fonctions privées

Pour implémenter les fonctions privées, on aura besoin du wrapper std::function. Les fonctions privées seront appelées par les fonctions publiques de la classe et ne seront pas visible à l’extérieur de la classe.

Vu que chaque fonction publique appelle une fonction privée, il y aura aussi 3 fonctions privées

  • contains_inside pour savoir si la clé est présente dans le dictionnaire
  • insert_inside pour insérer la clé dans le dictionnaire
  • erase_inside pour supprimer la clé du dictionnaire

Pour définir une fonction renvoyant un booléean et prenant en paramètre une clé de type T

std::function<bool(const T &key)>;

Si elle ne renvoie aucune valeur de retour, le type de la fonction est void

std::function<void(const T &key)>;

Ce qui donne :

template<typename T>
class DictionaryContainer {

    using boolFunction =  std::function<bool(const T &key)>;
    using voidFunction =  std::function<void(const T &key)>;

    const boolFunction contains_inside;
    const voidFunction insert_inside;
    const voidFunction erase_inside;

Si on reprend le code plus haut, la fonction publique contains va être complété comme suit :

 bool contains(const T &key) const {
        return contains_inside(key);
 }

Constructeur

Le constructeur prend 3 fonctions en paramètre afin d’initialiser les 3 fonctions privées :

  • contaisFunction
  • insertFunction
  • eraseFunction
 DictionaryContainer(const boolFunction &containsFunction, const voidFunction &insertFunction,
                        const voidFunction &eraseFunction)
            : contains_inside(containsFunction), insert_inside(insertFunction), erase_inside(eraseFunction) {}

Code de la classe

Ci-dessous, le code complet de la classe DictionaryContainer.

template<typename T>
class DictionaryContainer {

    using boolFunction =  std::function<bool(const T &key)>;
    using voidFunction =  std::function<void(const T &key)>;

    const boolFunction contains_inside;
    const voidFunction insert_inside;
    const voidFunction erase_inside;

public:

    DictionaryContainer(
        const boolFunction &containsFunction, 
        const voidFunction &insertFunction,
        const voidFunction &eraseFunction) : contains_inside(containsFunction), insert_inside(insertFunction), erase_inside(eraseFunction) {}

  
    bool contains(const T &key) const {
        return contains_inside(key);
    }

    /**
     * @brief Appelle la fonction d'insertion avec la clé donnée
     * @param key
     */
    void insert(const T &key) const {
        insert_inside(key);
    }

    /**
     * @brief Delete a key from the dictionary
     * @param key
     */
    void erase(const T &key) const {
        erase_inside(key);
    }
};

Etape 2 - fonction de test

Pour créer un objet de la classe, il suffit de le créer en lui passant en paramètre les fonctions correspondantes au conteneur choisi.

unordered_set

Avec unordered_set, les fonctions de recherche, insertion et de suppression sont find, insert et erase

void testUnordered(){

    // Creation
    unordered_set<string> test;
    DictionaryContainer<string> dc([&test](const string &KEY) { return test.find(KEY) != test.end(); },
                                   [&test](const string &KEY) { test.insert(KEY); },
                                   [&test](const string &KEY) { test.erase(KEY); });
    dc.insert("Test");
    dc.insert("Alfred");
    dc.insert("Gimmove");
    assert(dc.contains("Gimmove") == true);
    dc.erase("Gimmove");
    assert(dc.contains("Gimmove") == false);
}

vector

	// Creation
    vector<string> test;
    DictionaryContainer<string> dc([&test](const string &KEY) { return binary_search(test.begin(), test.end(), KEY); },
                                   [&test](const string &KEY) { test.push_back(KEY); },
                                   [&test](const string &KEY) { test.erase(lower_bound(test.begin(), test.end(), KEY)); });

    dc.insert("Test");
    dc.insert("Alfred");
    dc.insert("Gimmove");
    sort(test.begin(), test.end());
    assert(dc.contains("Gimmove") == true);
    dc.erase("Gimmove");
    assert(dc.contains("Gimmove") == false);

Avec vector, les fonctions de recherche, d’insertion et de suppression sont binary_search, push_back et erase.

A noter que l’utilisation de binary_search et lower_bound est conditionné au fait que le vector est trié, par exemple en appliquant sort.

Source

You might also enjoy