M2103-TP8-Exo-4

Travail à effectuer

Créez le projet FunctorIntegrTrapezes.

Dans votre fichier main.cpp copier le fichier IntegrTrapezes.cpp Intégration par la méthode des trapèzes d’un ancien TP.

/**
 *
 * @file    IntegrTrapezes.cpp
 *
 * @authors D. Mathieu
 *
 * @date    06/11/2007
 *
 * @version V1.0
 *
 * @brief   Integration par la methodes des trapezes au moyen de
 *             pointeurs de fonctions

 *
 **/
#include <iostream>
#include <cmath>      // cos(), sin(), M_PI_2

using namespace std;

namespace
{
    typedef double (*fdeX) (double);

    double integrTrapezes (fdeX f, double a, double b, unsigned n)
    {
        double s     = (f (a) + f (b)) / 2.0; 
        double delta = (b - a) / double (n);

        for ( ; --n; ) s += f (a += delta);

        return s * delta;

    } // integrTrapezes

    void testIntegrTrapezes (void)
    {
        cout << "IntegrTrapezes : \n\n";

        cout << "Methode des trapezes : \n";

        cout << "S (cos (x)) entre 0 et +Pi/2  avec   5 intervalles = " 
             << integrTrapezes (cos, 0, M_PI_2,   5) << '\n';

        cout << "S (cos (x)) entre 0 et +Pi/2  avec  10 intervalles = " 
             << integrTrapezes (cos, 0, M_PI_2,  10) << '\n';

        cout << "S (cos (x)) entre 0 et +Pi/2  avec  50 intervalles = " 
             << integrTrapezes (cos, 0, M_PI_2,  50) << '\n';

        cout << "S (cos (x)) entre 0 et +Pi/2  avec 100 intervalles = " 
             << integrTrapezes (cos, 0, M_PI_2, 100) << '\n';

        cout << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec   5 intervalles = " 
             << integrTrapezes (sin, -M_PI_2, 0,   5) << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec  10 intervalles = " 
             << integrTrapezes (sin, -M_PI_2, 0,  10) << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec  50 intervalles = " 
             << integrTrapezes (sin, -M_PI_2, 0,  50) << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec 100 intervalles = " 
             << integrTrapezes (sin, -M_PI_2, 0, 100) << '\n';

    } // TestIntegrTrapezes()

} // namespace anonyme

int main (void)
{
    testIntegrTrapezes ();

    return 0;

} // main()

Dans son espace de noms anonyme, ajoutez la classe générique abstraite IUnaryFunction suivante (écrite dans l’exercice Functors et algorithmes de recherche) :

    template <typename T, typename TRes>
    class IUnaryFunction
    {
      public :
        virtual ~IUnaryFunction (void) {}
        virtual TRes operator () (const T &) const noexcept = 0;

    }; // IUnaryFunction

Instanciez-la en déclarant le type UnFctor_dd pour que l’opérateur () puisse remplacer les fonctions sin() et cos().

Dérivez-la en deux classes FctorSin et CFctorCos où tout est public, en surchargeant l’opérateur () qui doit appeler respectivement les fonctions sin() et cos().

Modifiez en conséquence les fonctions integrTrapezes() et testIntegrTrapezes().

Compilez et testez.

Ne pas oublier de sauvegarder les fichiers sources du projet sur github.

M2103-TP8-Exo-4-Corrigé

/**
 *
 * @file     FunctorIntegrTrapezes.cpp
 *
 * @authors  D. Mathieu
 *
 * @date     07/12/2011
 *
 * @version  V1.0
 *
 * @brief    Integration par la methodes des trapezes au moyen de
 *             functor

 *
 **/
#include <iostream>
#include <cmath>     // cos(), sin(), M_PI_2

using namespace std;

#define classdef typedef

namespace
{
    template <typename T, typename TRes>
    class IUnaryFunction
    {
      public :
        virtual ~IUnaryFunction (void) {}
        virtual TRes operator () (const T &) const noexcept = 0;

    }; // IUnaryFunction

    typedef IUnaryFunction <double, double> UnFctor_dd;

    struct FctorCos : public UnFctor_dd
    {
        virtual ~FctorCos (void) {}
        virtual double operator() (const double & x) const noexcept
        {
            return cos (x);

        } // operator()

    }; // FctorCos

    struct FctorSin : public UnFctor_dd
    {
        virtual ~FctorSin (void) {}
        virtual double operator() (const double & x) const noexcept
        {
            return sin (x);

        } // operator()

    }; // FctorSin

    double integrTrapezes (const UnFctor_dd & f,
                           double a, double b, unsigned n)
    {
        double s     = (f (a) + f (b)) / 2.0;
        double delta = (b - a) / double (n);

        for ( ; --n; ) s += f (a += delta);

        return s * delta;

    } // integrTrapezes

    void testIntegrTrapezes (void)
    {
        cout << "IntegrTrapezes : \n\n";

        cout << "Methode des trapezes : \n";

        cout << "S (cos (x)) entre 0 et +Pi/2  avec   5 intervalles = "
             << integrTrapezes (FctorCos (), 0, M_PI_2,   5) << '\n';

        cout << "S (cos (x)) entre 0 et +Pi/2  avec  10 intervalles = "
             << integrTrapezes (FctorCos (), 0, M_PI_2,  10) << '\n';

        cout << "S (cos (x)) entre 0 et +Pi/2  avec  50 intervalles = "
             << integrTrapezes (FctorCos (), 0, M_PI_2,  50) << '\n';

        cout << "S (cos (x)) entre 0 et +Pi/2  avec 100 intervalles = "
             << integrTrapezes (FctorCos (), 0, M_PI_2, 100) << '\n';

        cout << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec   5 intervalles = "
             << integrTrapezes (FctorSin (), -M_PI_2, 0,   5) << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec  10 intervalles = "
             << integrTrapezes (FctorSin (), -M_PI_2, 0,  10) << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec  50 intervalles = "
             << integrTrapezes (FctorSin (), -M_PI_2, 0,  50) << '\n';

        cout << "S (sin (x)) entre -Pi/2 et 0  avec 100 intervalles = "
             << integrTrapezes (FctorSin (), -M_PI_2, 0, 100) << '\n';

    } // testIntegrTrapezes()

} // namespace

int main (void)
{
    testIntegrTrapezes ();

    return 0;

} // main()

M2103-TP7-Exo-1

Dans certains cas nous allons avoir affaire à des classes génériques dérivées d’un paramètre de généricité.

Exemple :

template <class T>
class CX : public T
{

};//

Les spécifications de telles classes sont évidemment que le paramètre de généricité soit une classe dérivable, ce qui interdit tous les types de base du C : ints ou floats par exemple.

Cet inconvénient peut être levé si c’est vraiment nécessaire, en encapsulant les types de base du C (int, char, double, etc.) dans de véritables classes, comme le fait le langage Java.

Le mécanisme étant identique pour tous les types de base, il doit être conçu générique dès le début.

Travail à effectuer

Créer le projet CTypeBase.

Dans le fichier TypesBase.hpp, écrire la classe générique TypeBase dans l’espace de noms std.

Lui ajouter :

  • une donnée membre myVal du type générique T,
  • un constructeur explicit ayant un paramètre de type T avec une valeur par défaut,
  • un opérateur de conversion dans le type T.

Parce que les corps des fonctions sont particulièrement simples, ils seront exceptionnellement mis directement à l’intérieur de la déclaration de la classe.

A la suite de la déclaration de la classe TypeBase, on peut l’instancier avec différents types de base au moyen de l’instruction typedef.
Il est possible de profiter de l’occasion pour rendre les types de base ainsi encapsulés indépendants de l’implémentation.
Rappelons en effet que les implémentations des types de base du C/C++ sont variables : un int peut être codé sur 2 ou 4 octets par exemple, ce qui peut poser quelques problèmes de portabilité ou au moins froisser certaines susceptibilités …
Il suffit de chercher une fois pour toutes dans les nombreux fichiers des bibliothèques fournies avec le compilateur C++, à quel endroit est défini un type “entier sur 32 bits” par exemple.
Ici, il s’agit du fichier <cstdint>, qui définit entre autres les types int8_t, int16_t, int32_t et int64_t, ainsi que leur version non signée.
Ajouter les instanciations des types Short, Integer et Character respectivement int16_t, int32_t et uint8_t au fichier TypeBase.hpp.

L’idéal serait de pouvoir se servir de ces nouveaux types en lieu et place des types de base int et short.

Par exemple, on devrait pouvoir écrire la séquence suivante :

for (Integer i; cin >> i; ) cout << i << "; ";

L’injecteur ne pose aucun problème car le compilateur, n’ayant pas de surcharge de l’opérateur << qui accepte un Integer en second paramètre, cherche à convertir le type Integer en un des types pour lesquels il connaît une surcharge.

Ici, il s’agit du type int, grâce à l’opérateur demandé ci-dessus.

En revanche, l’extracteur ne peut fonctionner comme on l’espère : il attend comme second paramètre une référence d’objet, dans lequel il peut transférer la valeur lue dans le flux.

Or l’opérateur demandé ci-dessus renvoie un int, c’est-à-dire une valeur numérique dans laquelle on ne peut évidemment pas mettre une autre valeur.

La solution est donc que l’opérateur de conversion renvoie une référence d’entier (l’adresse de la donnée membre) dans laquelle l’extracteur rangera la valeur lue.

Copier le fichier de test TestTypesBase.cpp dans votre fichier main.cpp

/**
 *
 * @file     TestTypesBase.cpp
 *
 * @authors  M. Laporte
 *
 * @date     07/05/2018
 *
 * @version  V2.0
 *
 **/
#include <iostream>
#include <cassert>

#include "TypesBase.hpp"        // Character

using namespace std;
#define classdef typedef

namespace 
{
    void testTypesBase (void)
    {
        cout << "TestTypesBase : ";

        // Verification de l'arithmetique des entiers avec la classe
        //   Short

        Short s1 (12), s2 (34);
        assert (s1 == 12);
        assert (s2 == 34);
        assert ((s1 + s2) == 46);
        assert (s1++ == 12);
        assert (++s1 == 14);

        cout << "OK\n";

        // Verification du fonctionnement de l'injecteur
        
        cout << "Saisir un Short : ";
        cin >> s1;
        
        cout << "s1 = " << s1 << endl;
        
    }// testTypesBase ()

} // namespace

int main ()
{
    testTypesBase();    // Attention : exception bad_alloc possible ...

    return 0;

} // main()

Compiler et tester.

M2103-TP7-Exo-1-Corrigé

/**
 *
 * @file    TypesBase.hpp
 *
 * @authors D. Mathieu, M. Laporte
 *
 * @date    26/04/2010
 *
 * @version V1.0
 *
 * @brief   Encapsulation de quelques types de base
 *
 **/
#pragma once

#include <cstdint>    // int32_t, int16_t

namespace std
{
    template <typename T>
    class TypeBase
    {
        T myVal;

      public :
        explicit TypeBase (T val = T ()) noexcept : myVal (val) {}
        operator T & () noexcept { return myVal; }
        operator const T & () const noexcept { return myVal; }
  
    }; // CTypeBase

typedef TypeBase <int32_t> Integer;
typedef TypeBase <int16_t> Short;
typedef TypeBase <uint8_t> Character;

} // namespace std

M2103-TP7-Exo-2

Le passage d’une fonction (ou plutôt d’un pointeur de fonction) en paramètre d’une autre fonction est une technique très ancienne, puisqu’elle date de l’apparition du langage C.

Il est indispensable de la connaître pour cette raison (c’est la seule possibilité de “généricité” de traitement en C), et elle est encore utilisée par certains langages comme Ada95.

Cependant, le C++ offre un moyen beaucoup plus élégant, et surtout plus performant : les functors (voir l’amphi correspondant).

Au lieu de passer une fonction f1() en paramètre d’une autre fonction f2(), il suffit de créer une classe contenant cette fonction f1() comme méthode (fonction-membre, opération), d’instancier cette classe en un objet obj, de passer cet objet à la fonction f2(), qui appellera la méthode f1() de son paramètre : param.f1 (...).

class Obj
{
    // ...
  public :
    ... f1 (...) 
    { 
        ... 

    } // f1

    // ...

}; // Obj

void f2 (Obj & param)
{
    // ...
    ... param.f1 (...);
    // ...

} // F2()

Le dernier obstacle est l’obligation qu’a la fonction f2() de connaître l’identificateur de la fonction f1().

Il est levé en surchargeant l’opérateur () dans la classe et en donnant à cet opérateur le même profil et le même corps que la fonction f1().

Un objet de la classe ainsi modifiée est appelé un “objet-fonction” ou plus simplement un functor

Ainsi, la fonction f2() se contente d’appeler param (...) au lieu de param.f1 (...).

Travail à effectuer

Créer le projet Functor.

remplacer le contenu du fichier main.cpp par celui du fichier TestFunctor.cpp

/**
 *
 * @file   TestFunctor.cpp
 *
 * @authors M. Laporte, D. Mathieu
 *
 * @date    27/04/2010
 *
 * @version V1.0
 *
 * @brief   Premier functor 
 *
 **/
#include <iostream>
#include <cassert>
#include <cctype>  // tolower(), toupper()
#include <string>

using namespace std;

namespace 
{
    class ToLower
    {
        // ToDo

    }; // ToLower

    typedef int (* fctInt2Int_t) (int);
    string & moulinette (string & str, fctInt2Int_t transf)
    {
        for (string::size_type i (str.size ()); i--; )
            str [i] = transf (str [i]);

        return str;

    } // moulinette()

    void testFunctor (void)
    {
        cout << "Functor : ";

        string ligne  ("AZECv qrgWSg wrV  wfdgWFDHG  wdfGWXCV");
        string minusc ("azecv qrgwsg wrv  wfdgwfdhg  wdfgwxcv");

        // ToDo 
        assert (minusc == moulinette (ligne, ...));

        cout << "OK\n";

    } // testFunctor()

} // namespace anonyme

int main (void)
{
    /*      */    testFunctor ();     /*           */

    return 0;

} // main()

Complétez la classe ToLower possédant :

  • un destructeur virtuel,
  • une surcharge (virtuelle) de l’opérateur (), ayant le même profil que la fonction C tolower() :
    int tolower (int); 
    

    qui se contente d’appeler cette dernière et d’en renvoyer le résultat.

Modifiez la fonction moulinette() en remplaçant le second paramètre par un functor de la classe ToLower, et complétez l’appel de la fonction assert() dans la fonction testFunctor().

Remarque : la lourdeur de mise en œuvre est à peu près du même ordre que celle de l’utilisation des pointeurs de fonction, et, pour le moment, la fonction moulinette() ne peut faire qu’une seule opération : mettre en
minuscules.

Nous verrons ultérieurement les avantages décisifs des functors.

M2103-TP7-Exo-2-Corrigés

 
/**
 *
 * @file   TestFunctor.cpp
 *
 * @authors M. Laporte, D. Mathieu
 *
 * @date    07/12/2011
 *
 * @version V1.0
 *
 * @brief   Premier functor 
 *
 **/
#include <iostream>
#include <string>
#include <cctype>  // tolower(), toupper()
#include <cassert>

using namespace std;

namespace 
{
    class ToLower
    {
      public :
        virtual ~ToLower (void) {}
        virtual int operator () (int caract) const
        {
            return tolower (caract);

        } // operateur()

    }; // ToLower

    string & moulinette (string & str, const ToLower & transf)
    {
        for (string::size_type i (str.size ()); i--; )
            str [i] = transf (str [i]);

        return str;

    } // moulinette()

    void testFunctor (void)
    {
        cout << "Functor : ";

        string ligne  ("AZECv qrgWSg wrV  wfdgWFDHG  wdfGWXCV");
        string minusc ("azecv qrgwsg wrv  wfdgwfdhg  wdfgwxcv");

        assert (minusc == moulinette (ligne, ToLower()));

        cout << "OK\n";

    } // testFunctor()

} // namespace

int main (void)
{
    /*      */    testFunctor();     /*           */

    return 0;

} // main()

M2103-TP7-Exo-3

Dans l’exercice précédent, nous avons signalé que nous avons perdu en généralité par rapport à la technique de passage de pointeur de fonction, puisque la fonction moulinette() est limitée à un seul type de traitement.

Nous allons maintenant lui rendre sa généralité.

Pour mettre la chaîne en majuscules par exemple, il faut commencer par écrire la nouvelle classe ToUpper, et y surcharger l’opérateur () adéquat.

Pour que la fonction moulinette() puisse mettre la chaîne en majuscules ou en minuscules (ou effectuer toute autre transformation), il faut utiliser le polymorphisme.

Travail à effectuer

Créer le projet FunctorAbstrait.

Dans votre fichier main.cpp copier le fichier TestFunctorAbstrait.cpp

/**
 *
 * @file    TestFunctorAbstrait.cpp
 *
 * @authors M. Laporte, D. Mathieu
 *
 * @date    27/04/2010
 *
 * @version V1.0
 *
 * @brief   functor abstrait
 *
 **/
#include <string>
#include <iostream>
#include <cctype>  // ispunct(), islower(), isalpha()
                   // tolower(), toupper()
#include <cassert>

using namespace std;

namespace 
{
    class ToLower
    {
      public :
        virtual ~ToLower (void) {}
        virtual int operator () (int caract) const
        {
            return tolower (caract);

        } // operateur()

    }; // CToLower

    string & moulinette (string & str, const ToLower & transf)
    {
        for (string::size_type i (str.size ()); i--; )
            str [i] = transf (str [i]);

        return str;

    } // moulinette()

    void testFunctor (void)
    {
        cout << "Functor abstrait : ";

        string ligne     ("azert:;,.?GFDSQ");
        string minusc    ("azert:;,.?gfdsq");
        string majusc    ("AZERT:;,.?GFDSQ");
        string sansPunct ("AZERT     GFDSQ");

        // ToDo

        assert (minusc    == moulinette (ligne, ...));
        assert (majusc    == moulinette (ligne, ...));
        assert (sansPunct == moulinette (ligne, ...));

        cout << "OK\n";

    } // testFunctor ()

} // namespace anonyme

int main (void)
{
    /*      */    testFunctor ();     /*           */

    return 0;

} // main()

Dans l’espace de noms anonyme, ajoutez-lui la classe abstraite ITraitCar dont l’unique fonction-membre abstraite a le même profil que tolower(), toupper(), etc.

N’oubliez pas son destructeur virtuel !

Faites dériver publiquement la classe ToLower de ITraitCar.

Sur le même modèle, écrivez les nouvelles classes ToUpper et IgnPunct qui transforment respectivement les minuscules en majuscules et les caractères de ponctuation en espaces.

Modifiez le type du second paramètre de moulinette() en conséquence.

Le tour est joué !

Dans la fonction testFunctor (), complétez les instructions assert().

Compilez et testez.

M2103-TP7-Exo-3-Corrigés

 
/**
 *
 * @file    TestFunctorAbstrait.cpp
 *
 * @authors M. Laporte
 *
 * @date    07/05/2018
 *
 * @version V1.0
 *
 * @brief   functor abstrait
 *
 **/
#include <string>
#include <iostream>
#include <cctype>     // ispunct(), islower(), isalpha()
                      // tolower(), toupper()
#include <cassert>

using namespace std;

namespace 
{
    class ITraitCar
    {
      public :
        virtual ~ITraitCar (void) {}
        virtual int operator () (int caract) const = 0;

    }; // ITraitCar

    class ToLower  : public ITraitCar
    {
      public :
        virtual ~ToLower (void) {}
        virtual int operator () (int caract) const noexcept
        {
            return tolower (caract);

        } // operateur()

    }; // ToLower

    class ToUpper : public ITraitCar
    {
      public :
        virtual ~ToUpper (void) {}
        virtual int operator () (int caract) const noexcept
        {
            return toupper (caract);

        } // operateur()

    }; // ToUpper

    class IgnPunct : public ITraitCar
    {
      public :
        virtual ~IgnPunct (void) {}
        virtual int operator () (int caract) const noexcept
        {
            return ispunct (caract) ? ' ' : caract;

        } // operateur()

    }; // IgnPunct

    string & moulinette (string & str, const ITraitCar & transf)
    {
        for (string::size_type i (str.size()); i--; )
            str [i] = transf (str [i]);

        return str;

    } // moulinette()

    void testFunctor (void)
    {
        cout << "Functor abstrait : ";

        string ligne     ("azert:;,.?GFDSQ");
        string minusc    ("azert:;,.?gfdsq");
        string majusc    ("AZERT:;,.?GFDSQ");
        string sansPunct ("AZERT     GFDSQ");

        assert (minusc    == moulinette (Ligne, ToLower ()));
        assert (majusc    == moulinette (Ligne, ToUpper ()));
        assert (sansPunct == moulinette (Ligne, IgnPunct()));

        cout << "OK\n";

    } // testFunctor()

} // namespace

int main (void)
{
    /*      */    testFunctor ();     /*           */

    return 0;

} // main()

M2103-TP7-Exo-4

L’utilisation d’un functor à la place d’un pointeur de fonction, comme nous venons de le voir, offre une très importante possibilité : il peut contenir des données-membres susceptibles d’être modifiées, et qui peuvent changer dynamiquement le comportement de l’opérateur ().

De même, si le functor est une donnée-résultat, certaines de ces données-membres peuvent être récupérées après l’appel de moulinette().

C’est ce que nous allons faire ici.

Travail à effectuer

Créer le projet FunctorInOut.

Remplacer le contenu de votre fichier main.cpp par le contenu du fichier TestFunctorAbstraitCorr.cpp (corrigé de l’exercice précédent) :

/**
 *
 * @file    TestFunctorAbstraitCorr.cpp
 *
 * @authors M. Laporte
 *
 * @date    07/05/2018
 *
 * @version V1.0
 *
 * @brief   functor abstrait
 *
 **/
#include <string>
#include <iostream>
#include <cctype>    // ispunct(), islower(), isalpha()
                     // tolower(), toupper()
#include <cassert>

using namespace std;

namespace 
{
    class ITraitCar
    {
      public :
        virtual ~ITraitCar (void) {}
        virtual int operator () (int caract) const = 0;

    }; // ITraitCar

    class ToLower  : public ITraitCar
    {
      public :
        virtual ~ToLower (void) {}
        virtual int operator () (int caract) const noexcept
        {
            return tolower (caract);

        } // operateur()

    }; // ToLower

    class ToUpper : public ITraitCar
    {
      public :
        virtual ~ToUpper (void) {}
        virtual int operator () (int caract) const noexcept
        {
            return toupper (caract);

        } // operateur()

    }; // ToUpper

    class IgnPunct : public ITraitCar
    {
      public :
        virtual ~IgnPunct (void) {}
        virtual int operator () (int caract) const noexcept
        {
            return ispunct (caract) ? ' ' : caract;

        } // operateur()

    }; // IgnPunct

    string & moulinette (string & str, const ITraitCar & transf)
    {
        for (string::size_type i (str.size()); i--; )
            str [i] = transf (str [i]);

        return str;

    } // moulinette()

    void testFunctor (void)
    {
        cout << "Functor abstrait : ";

        string ligne     ("azert:;,.?GFDSQ");
        string minusc    ("azert:;,.?gfdsq");
        string majusc    ("AZERT:;,.?GFDSQ");
        string sansPunct ("AZERT     GFDSQ");

        assert (minusc    == moulinette (ligne, ToLower ()));
        assert (majusc    == moulinette (ligne, ToUpper ()));
        assert (sansPunct == moulinette (ligne, IgnPunct()));

        cout << "OK\n";

    } // testFunctor()

} // namespace

int main (void)
{
    /*      */    testFunctor ();     /*           */

    return 0;

} // main()

Dans la classe ITraitCar, ajoutez :

  • la donnée-membre myCpt, qui est un compteur modifiable par l’opérateur () (on ne sait pas encore comment, puisque la fonction est abstraite !),
  • un constructeur qui initialise à 0 la donnée-membre myCpt,
  • l’accesseur et le modifieur correspondants.

Dans la classe ToUpper, modifiez l’opérateur () pour qu’il cumule le nombre de fois qu’il a été appelé.

Dans la classe ToLower, modifiez l’opérateur () pour qu’il cumule le nombre de fois qu’il a été appelé sur une lettre (minuscule ou majuscule).

Dans la classe IgnPunct, modifiez l’opérateur () pour qu’il cumule le nombre de caractères remplacés.

Dans la fonction testFunctor(), vérifiez au moyen d’un assert() le nombre de transformations effectuées après chaque appel à moulinette().

Remarque

la réalisation exacte de l’énoncé ci-dessus conduit à une erreur de compilation.

En effet, l’opérateur () modifie le contenu de la donnée-membre myCpt, donc il ne devrait pas être suivi de const.

De même, le functor passé en paramètre à la fonction moulinette() est une donnée-résultat, puisqu’on en récupère le contenu après traitement.

Deux solutions peuvent être envisagées :

  1. transformer l’opérateur () en accesseur/modifieur (suppression de const de son profil) et transformer le paramètre formel functor de moulinette() en donnée-résultat (supprimer son qualifieur const),
  2. qualifier la donnée membre myCpt de la classe mère “mutable“, car l’opérateur () est déclaré const et pourtant,
    il modifie l’objet …
    Cette solution correspond à l’idée que ces comptages sont faits “à l’insu” de l’utilisateur, par exemple dans une optique de mise au point ou de profiling de programme (étude du comportement d’un programme, statistiques d’utilisation, etc… en vue de son optimistion).

C’est cette dernière solution que nous présentons dans le corrigé.

M2103-TP7-Exo-4-Corrigé

 
/**
 *
 * @file    TestFunctorInOut.cpp
 *
 * @authors M. Laporte, D. Mathieu
 *
 * @date    07/12/2011
 *
 * @version V1.0
 *
 * @brief   functor abstrait
 *
 **/
#include <string>
#include <iostream>
#include <cctype>   // ispunct(), islower(), isalpha()
                    // tolower(), toupper()
#include <cassert>

using namespace std;

namespace 
{
    class ITraitCar
    {
      protected :
        mutable unsigned myCpt;

      public :
        ITraitCar (void) : myCpt (0) {}

        unsigned getCpt (void)   const noexcept { return myCpt;  }
        void     setCpt (unsigned cpt) noexcept { myCpt = cpt;   }

        virtual ~ITraitCar (void) {}
        virtual int operator () (int caract) const = 0;

    }; // ITraitCar

    class CToUpper : public ITraitCar
    {
      public :
        virtual ~CToUpper (void) {}
        virtual int operator () (int caract) const
        {
            ++myCpt;

            return toupper (caract);

        } // operateur()

    }; // CToUpper

    class CIgnPunct : public ITraitCar
    {
      public :
        virtual ~CIgnPunct (void) {}
        virtual int operator () (int caract) const
        {
            return ispunct (caract) ? ++myCpt, ' ' : caract;

        } // operateur()

    }; // CIgnPunct

    class CToLower : public ITraitCar
    {
      public :
        virtual ~CToLower (void) {}
        virtual int operator () (int caract) const
        {
            if (isalpha (caract)) ++myCpt;
            return tolower (caract);

        } // operateur()

    }; // CToLower

    string & moulinette (string & str, const ITraitCar & transf)
    {
        for (string::size_type i (str.size ()); i--; )
            str [i] = transf (str [i]);

        return str;

    } // moulinette()

    void testFunctor (void)
    {
        cout << "FunctorInOut : ";

        string ligne     ("Phrase ... Avec,,:,; pOnCtUaTiOn");
        string minusc    ("phrase ... avec,,:,; ponctuation");
        string majusc    ("PHRASE ... AVEC,,:,; PONCTUATION");
        string sansPunct ("PHRASE     AVEC      PONCTUATION");

        ToLower  toLower;
        assert (minusc    == moulinette (ligne, toLower ));
        assert (21 == toLower.getCpt());

        ToUpper  toUpper;
        assert (majusc    == moulinette (ligne, toUpper ));
        assert (32 == toUpper.getCpt());

        IgnPunct ignPunct;
        assert (sansPunct == moulinette (ligne, ignPunct));
        assert ( 8 == ignPunct.getCpt());

        cout << "OK\n";

    } // testFunctor()

} // namespace

int main (void)
{
    /*      */    testFunctor ();     /*           */

    return 0;

} // main()