M2103-TP8-Exo-2

Il existe une fonction de recherche générique standard très
puissante :

template <class InputIterator, class Predicate>
InputIterator find_if (InputIterator first, InputIterator last,
                       Predicate pred);

Comme son nom l’indique, elle recherche, entre les itérateurs first et last d’un conteneur, la position du premier élément qui satisfait une condition, représentée par le functor pred qui doit être un prédicat.

Cela signifie que la surcharge de l’opérateur () a le
profil suivant :

    bool operator () const (const T &);

Le principe en est le même que précédemment.

Travail à effectuer

Créer le projet FunctorFind.

Copier dans le fichier main.cpp le fichier de test FunctorSort.cpp FunctorSort.cpp, qui est le corrigé de l’exercice FunctorSort Functors et algorithmes de tri” ci-dessus.

/**
 *
 * @file     FunctorSort.cpp
 *
 * @authors  M. Laporte, D. Mathieu
 *
 * @date     07/12/2011
 *
 * @version  V1.0
 *
 **/
#include <string>
#include <vector>
#include <algorithm> // sort()
#include <iostream>

using namespace std;

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

    }; // ILessThanGen

    class Pers
    {
        string   myNom;
        unsigned myAge;

      public :
        Pers (const string & Nom, unsigned Age)
            : myNom (Nom), myAge (Age) {}

        const string & getNom (void) const noexcept { return myNom; }
        unsigned       getAge (void) const noexcept { return myAge; }

    private :
        ostream & display (ostream & os)  const
        {
            return os << getAge () << " - " << getNom ();

        } // display()

      public :
        friend ostream & operator << (ostream & os, const Pers & p)
        {
            return p.display (os);

        }

    }; // Pers

    class TriParAgeAsc : public ILessThanGen <Pers>
    {
      public :
        virtual ~TriParAgeAsc (void) noexcept {}

        virtual bool operator () (const Pers & p1, const Pers & p2)
                        const noexcept
        {
            return p1.getAge () < p2.getAge ();

        } // operator ()

    }; // TriParAgeAsc

    class TriParNomDesc : public ILessThanGen <Pers>
    {
      public :
        virtual ~TriParNomDesc (void) noexcept {}

        virtual bool operator () (const Pers & p1, const Pers & p2)
                        const noexcept
        {
            return p1.getNom () > p2.getNom ();

        } // operator ()

    }; // TriParNomDesc

    void functorSort (void)
    {
        cout << "FunctorSort : \n";

        typedef vector <Pers> CVPers;

        CVPers vPers;

        vPers.push_back ( Pers ("Charlotte", 21));
        vPers.push_back ( Pers ("Alfred",    12));
        vPers.push_back ( Pers ("Jean",      42));
        vPers.push_back ( Pers ("Noemie",    11));
        vPers.push_back ( Pers ("Berthe",    99));
        vPers.push_back ( Pers ("Agathe",    29));
        vPers.push_back ( Pers ("Sylvain",   42));
        vPers.push_back ( Pers ("Pierre",    75));

        cout << "\nTri par age croissant\n\n";

        sort (vPers.begin (), vPers.end (), TriParAgeAsc ());

        for (const Pers & personne : vPers)
            cout << personne << '\n';

        cout << "\nTri par nom decroissant\n\n";

        sort (vPers.begin (), vPers.end (), TriParNomDesc ());

        for (const Pers & personne : vPers)
            cout << personne << '\n';

    } // functorSort()

} // namespace

int main (void)
{
    functorSort ();

    return 0;

} // main()

Remplacez la classe ILessThanGen par IPredicatGen et supprimez tout ce qui concerne les tris.

Dérivez publiquement cette classe en SelParTrancheAge, qui possède :

  • les deux données-membres myAgeMin et myAgeMax,
  • un constructeur qui permet de les initialiser,
  • un destructeur virtuel,
  • la surcharge de l’opérateur (), qui renvoie true lorsque l’âge de l’élément qui lui est passé en paramètre est dans l’intervalle [myAgeMin, myAgeMax].

Testez en utilisant et en complétant la fonction suivante :

    void functorFind (void)
    {
        cout << "FunctorFind : \n";

        typedef vector <Pers> CVPers;
        CVPers vPers;

        vPers.push_back ( Pers ("Charlotte", 21));
        vPers.push_back ( Pers ("Alfred",    12));
        vPers.push_back ( Pers ("Jean",      42));
        vPers.push_back ( Pers ("Noemie",    11));
        vPers.push_back ( Pers ("Berthe",    99));
        vPers.push_back ( Pers ("Agathe",    29));
        vPers.push_back ( Pers ("Sylvain",   42));
        vPers.push_back ( Pers ("Pierre",    75));

        for (const Pers & personne : vPers)
            cout << personne << '\n';

        CVPers::const_iterator pos;

        cout << "\nRecherche sur  43 <= age <= 75 : ";

        pos = find_if (vPers.begin (), vPers.end (), // a completer
        if (vPers.end () ==pos)
            cout << "Aucun element ne correspond a ce critere\n";
        else
            cout << *pos << '\n';

        cout << "\nRecherche sur  43 <= age <= 45 : ";

        pos = find_if (vPers.begin (), vPers.end (), // a completer
        if (vPers.end () == pos)
            cout << "Aucun element ne correspond a ce critere\n";
        else
            cout << *pos << '\n';

        cout << '\n';

    } // functorFind()

Lorsque vous obtenez des résultats conformes à l’affichage attendu ci-dessous,

FunctorFind : 
21 - Charlotte
12 - Alfred
42 - Jean
11 - Noemie
99 - Berthe
29 - Agathe
42 - Sylvain
75 - Pierre

Recherche sur  43 <= age <= 75 : 75 - Pierre

Recherche sur  43 <= age <= 45 : Aucun element ne correspond a ce critere
  • dérivez publiquement la classe IPredicatGen en SelParNomMin, qui possède :
    • la donnée-membre myNomMin,
    • un constructeur qui permet de l’initialiser,
    • un destructeur virtuel,
    • la surcharge de l’opérateur (), qui renvoie true lorsque le nom de l’élément qui lui est passé
      en paramètre est supérieur à celui de myNomMin.
  • ajoutez à la fonction functorFind() la séquence suivante :
            cout << "\nRecherche sur nom > Noemie : ";
    
            pos = find_if (vPers.begin (),  vPers.end (), // a completer
    
            if (vPers.end () == pos)
                cout  << "Aucun element ne correspond a ce critere\n";
            else
                cout << *pos << '\n';
    
            cout  << "\nRecherche sur nom > alfred : ";
    
            pos = find_if (vPers.begin (),  vPers.end (), // a completer
    
            if (vPers.end () == pos)
                cout  << "Aucun element ne correspond a ce critere\n";
            else
                cout << *pos << '\n';
    

Vous devez obtenir des résultats conformes à la deuxième partie de l’affichage attendu ci-dessous.

Lorsque les affichages sont corrects, une dernière modification est proposée : on remarque que la classe IPredicatGen manque encore de généralité, car le résultat est toujours un booléen (c’est pour cela que c’est un prédicat).

En réalité, dans certaines applications, on peut supposer avoir besoin de functors à un seul paramètre qui renvoient des types différents du type booléen.

Il suffit d’ajouter en second paramètre de généricité le type de retour : cela s’appelle une fonction-objet unaire ou unary function.

Ajoutez la classe IUnaryFunction, copie de IPredicatGen, à laquelle est ajouté le second paramètre de généricité TRes représentant le type de retour de la fonction.

Modifiez les deux classes SelParTrancheAge et SelParNomMin, en en faisant des UnaryFunction‘s et tester à nouveau.

résultats supplémentaires attendus :

Recherche sur nom > Noemie : 42 - Sylvain

Recherche sur nom > alfred : Aucun element ne correspond a ce critere

M2103-TP8-Exo-2-Corrigé

/**
 *
 * @file     FunctorFind.cpp
 *
 * @authors  M. Laporte, D. Mathieu
 *
 * @date     07/12/2011
 *
 * @version  V1.0
 *
 **/
#include <string>
#include <vector>
#include <algorithm>  // sort()
#include <iostream>

using namespace std;

namespace
{
    template <typename T>
    class IPredicatGen
    {
      public :
        virtual ~IPredicatGen (void) {}
        virtual bool operator () (const T &) const = 0;

    }; // IPredicatGen

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

    }; // IUnaryFunction

    class Pers
    {
        string   myNom;
        unsigned myAge;

      public :
        Pers (const string & Nom, unsigned Age)
            : myNom (Nom), myAge (Age) {}

        const string & getNom (void) const noexcept { return myNom; }
        unsigned       getAge (void) const noexcept { return myAge; }

    private :
        ostream & display (ostream & os)  const
        {
            return os << getAge () << " - " << getNom ();

        } // display()

      public :
        friend ostream & operator << (ostream & os, const Pers & p)
        {
            return p.display (os);

        }

    }; // Pers

    class SelParTrancheAge : public IPredicatGen <Pers>
//  class SelParTrancheAge : public IUnaryFunction <Pers, bool>
    {
        unsigned myAgeMin;
        unsigned myAgeMax;
      public :
        SelParTrancheAge (unsigned ageMin, unsigned ageMax) : myAgeMin (ageMin), myAgeMax (ageMax) {}
        virtual ~SelParTrancheAge (void) noexcept {}

        virtual bool operator () (const Pers & p) const
        {
            return myAgeMin <= p.getAge () && p.getAge () <= myAgeMax;

        } // operator ()

    }; // SelParTrancheAge

    class SelParNomMin : public IPredicatGen <Pers>
//  class SelParNomMin : public IUnaryFunction <Pers, bool> 
    {
        string myNomMin;
      public :
        SelParNomMin (const string & nomMin) : myNomMin (nomMin) {}
        virtual ~SelParNomMin (void) noexcept {}

        virtual bool operator () (const Pers & p) const
        {
            return myNomMin < p.getNom ();

        } // operator ()

    }; // SelParNomMin

    void functorFind (void)
    {
        cout << "FunctorFind : \n";

        typedef vector <Pers> CVPers;
        CVPers vPers;

        vPers.push_back ( Pers ("Charlotte", 21));
        vPers.push_back ( Pers ("Alfred",    12));
        vPers.push_back ( Pers ("Jean",      42));
        vPers.push_back ( Pers ("Noemie",    11));
        vPers.push_back ( Pers ("Berthe",    99));
        vPers.push_back ( Pers ("Agathe",    29));
        vPers.push_back ( Pers ("Sylvain",   42));
        vPers.push_back ( Pers ("Pierre",    75));

        for (const Pers & personne : vPers)
            cout << personne << '\n';

        CVPers::const_iterator pos;

        cout << "\nRecherche sur  43 <= age <= 75 : ";

        pos = find_if (vPers.begin (), vPers.end (), SelParTrancheAge (43, 75));// a completer
        if (vPers.end () == pos)
            cout << "Aucun element ne correspond a ce critere\n";
        else
            cout << *pos << '\n';

        cout << "\nRecherche sur  43 <= age <= 45 : ";

        pos = find_if (vPers.begin (), vPers.end (), SelParTrancheAge (43, 45)); // a completer
        if (vPers.end () == pos)
            cout << "Aucun element ne correspond a ce critere\n";
        else
            cout << *pos << '\n';

        cout << '\n';

        cout << "\nRecherche sur nom > Noemie : ";

        pos = find_if (vPers.begin (),  vPers.end (), SelParNomMin ("Noemie"));// a completer

        if (vPers.end () == pos)
            cout  << "Aucun element ne correspond a ce critere\n";
        else
            cout << *pos << '\n';

        cout  << "\nRecherche sur nom > alfred : ";

        pos = find_if (vPers.begin (),  vPers.end (), SelParNomMin ("alfred"));// a completer

        if (vPers.end () == pos)
            cout  << "Aucun element ne correspond a ce critere\n";
        else
            cout << *pos << '\n';


    } // functorFind()

} // namespace

int main (void)
{
    functorFind ();

    return 0;

} // main()

M2103-TP8-Exo-3

Remarque préliminaire : cet exercice ne peut être fait qu’après les exercices précédents sur les functors.

Outre les fonctions-objets unaires, la bibliothèque standard C++ utilise des fonctions-objets binaires, ou binary functors, qui possèdent un paramètre supplémentaire, dont le type est lui aussi générique.

En revanche, elle n’utilise aucune fonction-objet à plus de deux paramètres.

C’est ce que nous aurions pu utiliser dans l’exercice Functor-comparateur abstrait générique qui n’est pas autre chose qu’un prédicat à deux paramètres de même type.

Nous allons donc le généraliser dans cet exercice.

D’autre part, nous avons montré que, selon les fonctions standard utilisées, il est nécessaire de développer pour une même classe et pour un même critère de comparaison à la fois un functor unaire et un functor binaire.

Par exemple, pour la relation d’ordre “age croissant” de la classe Pers, un functor binaire TriParAgeAsc est nécessaire à la fonction standard générique sort(), et un functor unaire SelParTrancheAge est nécessaire à la fonction standard générique find_if().

Nous allons montrer comment un functor binaire peut être simplement transformé en functor unaire sans réécriture.

Travail à effectuer

Créez le projet Adaptor.

Tri

Dans votr main.cpp copier le fichier de test :

/**
 *
 * @file     FunctorSort.cpp
 *
 * @authors  M. Laporte, D. Mathieu
 *
 * @date     07/12/2011
 *
 * @version  V1.0
 *
 **/
#include <string>
#include <vector>
#include <algorithm>  // sort()
#include <iostream>

using namespace std;

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

    }; // ILessThanGen

    class Pers
    {
        string   myNom;
        unsigned myAge;

      public :
        Pers (const string & Nom, unsigned Age)
            : myNom (Nom), myAge (Age) {}

        const string & getNom (void) const noexcept { return myNom; }
        unsigned       getAge (void) const noexcept { return myAge; }

    private :
        ostream & display (ostream & os)  const
        {
            return os << getAge () << " - " << getNom ();

        } // display()

      public :
        friend ostream & operator << (ostream & os, const Pers & p)
        {
            return p.display (os);

        }

    }; // Pers

    class TriParAgeAsc : public ILessThanGen <Pers>
    {
      public :
        virtual ~TriParAgeAsc (void) noexcept {}

        virtual bool operator () (const Pers & p1, const Pers & p2)
                        const noexcept
        {
            return p1.getAge () < p2.getAge ();

        } // operator ()

    }; // TriParAgeAsc

    class TriParNomDesc : public ILessThanGen <Pers>
    {
      public :
        virtual ~TriParNomDesc (void) noexcept {}

        virtual bool operator () (const Pers & p1, const Pers & p2)
                        const noexcept
        {
            return p1.getNom () > p2.getNom ();

        } // operator ()

    }; // TriParNomDesc

    void functorSort (void)
    {
        cout << "FunctorSort : \n";

        typedef vector <Pers> CVPers;

        CVPers vPers;

        vPers.push_back ( Pers ("Charlotte", 21));
        vPers.push_back ( Pers ("Alfred",    12));
        vPers.push_back ( Pers ("Jean",      42));
        vPers.push_back ( Pers ("Noemie",    11));
        vPers.push_back ( Pers ("Berthe",    99));
        vPers.push_back ( Pers ("Agathe",    29));
        vPers.push_back ( Pers ("Sylvain",   42));
        vPers.push_back ( Pers ("Pierre",    75));

        cout << "\nTri par age croissant\n\n";

        sort (vPers.begin (), vPers.end (), TriParAgeAsc ());

        for (const Pers & personne : vPers)
            cout << personne << '\n';

        cout << "\nTri par nom decroissant\n\n";

        sort (vPers.begin (), vPers.end (), TriParNomDesc ());

        for (const Pers & personne : vPers)
            cout << personne << '\n';

    } // functorSort()

} // namespace

int main (void)
{
    functorSort ();

    return 0;

} // main()

, qui est le corrigé de l’exercice Functors et algorithmes de tri.

Remplacez la classe ILessThanGen par la classe IBinaryFunction à trois paramètres de généricité (les types des deux paramètres et de la valeur de retour), selon le modèle suivant :

    template <typename T1, typename T2, typename TRes>
    class IBinaryFunction
    {
      public :
        typedef T1   first_argument_type;
        typedef T2   second_argument_type;
        typedef TRes result_type;

        ...

Attention : les identificateurs des types sont imposés par bind2nd(),

Modifiez en conséquence les classes TriParAgeAsc et TriParNomDesc.

Le profil d’une fonction de tri comme par exemple :

    template <typename Iter_t> 
    void selectSort (Iter_t deb, Iter_t fin, const LessThan & IsInf);

devrait être modifié en :

    template <typename Iter_t, typename T>
    void selectSort (Iter_t deb, Iter_t fin,
                    const IBinaryFunction <T, T, bool> & isInf);
</t,>

En revanche, l’utilisation de la fonction sort() ne nécessite aucune modification.

Compilez et testez.

Recherche

Les functors binaires que nous avons écrits devraient pouvoir être utilisés pour faire des recherches.

Par exemple, le prédicat TriParAgeAsc renvoie true lorsque l’âge du premier paramètre est inférieur ou égal à celui du second paramètre.

Nous souhaitons donc l’utiliser pour rechercher le premier élément dont l’âge est <= à un âge donné, par exemple 40, quel que soit le nom de ce dernier.

Il suffit donc de comparer successivement tous les éléments du conteneur à la constante Pers ("", 40).

Malheureusement, la fonction find_if() déjà étudiée précédemment, nécessite un functor unaire.

La bibliothèque fournit la fonction bind2nd (appelée un helper), qui renvoie un functor unaire à partir :

  • de son premier paramètre qui doit être un functor binaire (nous utiliserons ici une instance de la classe TriParAgeAsc),
  • de son second paramètre qui doit être un objet de même type que le second paramètre de l’opérateur () du functor binaire (ici un Pers, second paramètre de l’opérateur () de TriParAgeAsc).

Très peu de modifications sont nécessaires :

  • ajoutez le fichier inclus functional dans lequel est déclaré le helper bind2nd(),
  • ajoutez en fin de la fonction Adaptor() (functorSort ()) le code suivant à compléter :
            cout << "\nRecherche de la premiere personne d'age <= 40 : ";
    
            CVPers::const_iterator pos =
                find_if (vPers.begin (), vPers.end (),  // a completer
    
            if (vPers.end () == pos)
                cout << "Aucun element correspondant\n";
            else
                cout << *pos << '\n';
    
            cout << "\nRecherche de la premiere personne d'age<= 4 : ";
    
            pos = find_if (vPers.begin (), vPers.end (), // a completer
    
            if (vPers.end () == pos)
                cout << "Aucun element correspondant\n";
            else
                cout << *pos << '\n';
    

M2103-TP8-Exo-3-Corrigé

/**
 *
 * @file     Adaptor.cpp
 *
 * @authors  M. Laporte, D. Mathieu
 *
 * @date     07/12/2011
 *
 * @version  V1.0
 *
 **/
#include <string>
#include <vector>
#include <algorithm>  // sort()
#include <iostream>
#include <functional>

using namespace std;

namespace
{
    template <typename T1, typename T2, typename TRes>
    class IBinaryFunction
    {
      public :
        typedef T1   first_argument_type;
        typedef T2   second_argument_type;
        typedef TRes result_type;

        virtual ~IBinaryFunction (void) {}
        virtual TRes operator () (const T1 &, const T2 &) const = 0;

    }; // IBinaryFunction

    class Pers
    {
        string   myNom;
        unsigned myAge;

      public :
        Pers (const string & Nom, unsigned Age)
            : myNom (Nom), myAge (Age) {}

        const string & getNom (void) const noexcept { return myNom; }
        unsigned       getAge (void) const noexcept { return myAge; }

    private :
        ostream & display (ostream & os)  const
        {
            return os << getAge () << " - " << getNom ();

        } // display()

      public :
        friend ostream & operator << (ostream & os, const Pers & p)
        {
            return p.display (os);

        }

    }; // Pers

    class TriParAgeAsc : public IBinaryFunction <Pers, Pers, bool>
    {
      public :
        virtual ~TriParAgeAsc (void) noexcept {}

        virtual bool operator () (const Pers & p1, const Pers & p2)
                        const noexcept
        {
            return p1.getAge () < p2.getAge ();

        } // operator ()

    }; // TriParAgeAsc

    class TriParNomDesc : public IBinaryFunction <Pers, Pers, bool>
    {
      public :
        virtual ~TriParNomDesc (void) noexcept {}

        virtual bool operator () (const Pers & p1, const Pers & p2)
                        const noexcept
        {
            return p1.getNom () > p2.getNom ();

        } // operator ()

    }; // TriParNomDesc

    void adaptor (void)
    {
        cout << "FunctorSort : \n";

        typedef vector <Pers> CVPers;

        CVPers vPers;

        vPers.push_back ( Pers ("Charlotte", 21));
        vPers.push_back ( Pers ("Alfred",    12));
        vPers.push_back ( Pers ("Jean",      42));
        vPers.push_back ( Pers ("Noemie",    11));
        vPers.push_back ( Pers ("Berthe",    99));
        vPers.push_back ( Pers ("Agathe",    29));
        vPers.push_back ( Pers ("Sylvain",   42));
        vPers.push_back ( Pers ("Pierre",    75));

        cout << "\nTri par age croissant\n\n";

        sort (vPers.begin (), vPers.end (), TriParAgeAsc ());

        for (const Pers & personne : vPers)
            cout << personne << '\n';

        cout << "\nTri par nom decroissant\n\n";

        sort (vPers.begin (), vPers.end (), TriParNomDesc ());

        for (const Pers & personne : vPers)
            cout << personne << '\n';
        cout << "\nRecherche de la premiere personne d'age <= 40 : ";

        CVPers::const_iterator pos = find_if (vPers.begin (), vPers.end (),
                                              bind2nd (TriParAgeAsc (), Pers (" ",40)));// a completer

        if (vPers.end () == pos)
            cout << "Aucun element correspondant\n";
        else
            cout << *pos << '\n';

        cout << "\nRecherche de la premiere personne d'age<= 4 : ";

        pos = find_if (vPers.begin (), vPers.end (),
                       bind2nd (TriParAgeAsc (), Pers (" ", 4)));// a completer

        if (vPers.end () == pos)
            cout << "Aucun element correspondant\n";
        else
            cout << *pos << '\n';
    } // adaptor()

} // namespace

int main (void)
{
    adaptor ();

    return 0;

} // main()

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()