2. C++ Avancé




2.3. La Généricité

La Généricité est un concept des langages de haut niveau qui consiste à donner une seule version d'un algorithme capable d'opérer sur des données de types différents.

Il est possible de créer :

On utilise le mot clé template pour déclarer une fonction ou une classe générique en C++.

Il suffit ensuite d'instancier les types génériques par des types concrets (int, float, classe crée par l'utilisateur), cela permet de ne pas à avoir à réécrire le code si seul un type de donnée change. C'est souvent le cas pour les containers (tableau, liste, file, ...).

2.3.1. Fonction générique

On prend ici l'exemple de la fonction minimum qui retourne la valeur minimum entre deux valeurs :

  1. #include <iostream>
  2. #include <cstdlib>
  3. using namespace std;
  4.  
  5. /**
  6.  * function that computes minimum of two values
  7.  */
  8. template<class T>
  9. T minimum(T a, T b) {
  10.     return (a < b) ? a : b;
  11. }
  12.  
  13. /**
  14.  * function that computes minimum of two values
  15.  * using a Predicate, here a function of the form
  16.  * bool Predicate(T a, T b)
  17.  * or
  18.  * bool Predicate(T& a, T& b)
  19.  */
  20. template<class T, class Predicate>
  21. T minimum(T a, T b, Predicate p) {
  22.     return p(a, b) ? a : b;
  23. }
  24.  
  25. /**
  26.  * class Person with name and age
  27.  */
  28. class Person {
  29. protected:
  30.     string name;
  31.     int age;
  32.    
  33. public:
  34.     Person(string n, int a) : name(n), age(a) {
  35.     }
  36.    
  37.     string get_name() {
  38.         return name;
  39.     }
  40.    
  41.     int get_age() {
  42.         return age;
  43.     }
  44.    
  45.     /**
  46.      * redefinition of <<
  47.      */
  48.     friend ostream& operator<<(ostream& out, const Person& p) {
  49.         out << p.name << ", " << p.age;
  50.         return out;
  51.     }
  52.    
  53.     /**
  54.      * definition of less than operator two compare two person's age
  55.      *
  56.      */
  57.     friend bool operator<(const Person& lhs, const Person& rhs) {
  58.         return lhs.age < rhs.age;
  59.     }
  60. };
  61.  
  62. /**
  63.  * main function
  64.  */
  65. int main() {
  66.     // integer
  67.     int min_i = minimum(2, 1);
  68.     cout << "min int = " << min_i << endl;
  69.     // float
  70.     float min_f = minimum(3.0, 2.0);
  71.     cout << "min float = " << min_f << endl;
  72.  
  73.     // Person
  74.     // will only work if the operator < has been defined
  75.     // for the class Person
  76.     Person riri("riri", 10);
  77.     Person fifi("fifi", 11);
  78.  
  79.     Person p_min_age = minimum(riri, fifi);
  80.  
  81.     cout << "min age = " << p_min_age << endl;
  82.    
  83.     Person p_min_name = minimum(riri, fifi, [](Person& lhs, Person& rhs) {
  84.         return lhs.get_name() < rhs.get_name();
  85.     });
  86.    
  87.     cout << "min name = " << p_min_name << endl;
  88.  
  89.     return EXIT_SUCCESS;
  90. }
  91.  

La STL (Standard Template Library) du C++ repose sur la généricité. Les algorithmes liés aux structures de données de la STL utilisent donc la généricité.

Voici un autre exemple qui permet d'afficher le contenu d'un container de sequence de la STL :

min int = 1
min float = 2
min age = riri, 10
min name = fifi, 11
  1. #include <vector>
  2. #include <list>
  3. #include <iostream>
  4. #include <cstdlib>
  5. using namespace std;
  6.  
  7. /**
  8.  * function that prints the contents of a sequence
  9.  * container : vector, list, deque, ...
  10.  */
  11. template<typename Container>
  12. void print(Container& c) {
  13.     auto it = c.begin();
  14.     if (c.size() > 0) {
  15.         cout << "[ " << *it++;
  16.         while (it != c.end()) {
  17.             cout << " " << *it;
  18.             ++it;
  19.         }
  20.         cout << " ]" << endl;
  21.     } else {
  22.         cout << "<empty>" << endl;
  23.     }
  24. }
  25.  
  26. /**
  27.  * main function
  28.  */
  29. int main() {
  30.     // print contents of a vector
  31.     vector<int> v = { 1, 8, 4, 3, -7, 6 };
  32.     print(v);
  33.    
  34.     // print contents of a list
  35.     list<string> w = { "riri", "fifi", "loulou" };
  36.     print(w);
  37.    
  38.     return EXIT_SUCCESS;
  39. }
  40.  
[ 1 8 4 3 -7 6 ]
[ riri fifi loulou ]

On dispose également de la possibilité de créer des template de template :

  1. #include <vector>
  2. #include <list>
  3. #include <iostream>
  4. #include <cstdlib>
  5. using namespace std;
  6.  
  7. /**
  8.  * function that prints the contents of a sequence
  9.  * container : vector, list, deque, ...
  10.  * we use a template of template
  11.  */
  12. template< typename V,
  13.     template<typename T, typename A> class Container = std::vector>
  14. void print(Container<V, std::allocator<V> >& c) {
  15.     auto it = c.begin();
  16.     if (c.size() > 0) {
  17.         cout << "[ " << *it++;
  18.         while (it != c.end()) {
  19.             cout << " " << *it;
  20.             ++it;
  21.         }
  22.         cout << " ]" << endl;
  23.     } else {
  24.         cout << "<empty>" << endl;
  25.     }
  26. }
  27.  
  28. /**
  29.  * main function
  30.  */
  31. int main() {
  32.     // print contents of a vector
  33.     vector<int> v = { 1, 8, 4, 3, -7, 6 };
  34.     print(v);
  35.    
  36.     // print contents of a list
  37.     list<string> w = { "riri", "fifi", "loulou" };
  38.     print(w);
  39.    
  40.     return EXIT_SUCCESS;
  41. }
  42.  

2.3.2. Classe générique

Il est préférable de déclarer l'ensemble des méthodes dans un fichier header d'extension .h :

  1. #ifndef VECTOR_H
  2. #define VECTOR_H
  3.  
  4. /**
  5.  ***********************************************
  6.  * Vector of elements
  7.  ***********************************************
  8.  */
  9. template<class T>
  10. class Vector {
  11. // =============================================
  12. // data members
  13. // =============================================
  14. protected:
  15.     // maximum number of elements
  16.     int max_size;
  17.     // dynamic array of elements
  18.     T *elements;
  19.    
  20. // =============================================
  21. // methods
  22. // =============================================   
  23. public:
  24.     // -----------------------------------------
  25.     // default constructor with 10 elements
  26.     // -----------------------------------------
  27.     Vector(int sz = 10) {
  28.         max_size = sz;
  29.         elements = new T [ max_size ];
  30.     }
  31.    
  32.     // -----------------------------------------
  33.     // copy constructor
  34.     // -----------------------------------------
  35.     Vector(Vector& v) {
  36.         max_size = v.max_size;
  37.         elements = new T [ max_size ];
  38.         for (int i=0; i<max_size; ++i) {
  39.             elements[i] = v.elements[i];
  40.         }
  41.     }
  42.    
  43.     // -----------------------------------------
  44.     // assignment operator
  45.     // -----------------------------------------
  46.     Vector& operator=(Vector& v) {
  47.         if (&v != this) {
  48.             delete [] elements;
  49.             max_size = v.max_size;
  50.             elements = new T [ max_size ];
  51.             for (int i=0; i<max_size; ++i) {
  52.                 elements[i] = v.elements[i];
  53.             }
  54.         }
  55.         return *this;
  56.     }
  57.    
  58.     // -----------------------------------------
  59.     // destructor
  60.     // -----------------------------------------
  61.     ~Vector() {
  62.         delete [] elements;
  63.     }
  64.    
  65.     // -----------------------------------------
  66.     // accessor
  67.     // -----------------------------------------
  68.     T& operator[](int n) {
  69.         return elements[n];
  70.     }
  71.    
  72.     // -----------------------------------------
  73.     // getter
  74.     // -----------------------------------------
  75.     int get_size() {
  76.         return max_size;
  77.     }
  78. };
  79. #endif
  80.  
  1. #include "generic_vector.h"
  2. #include <iostream>
  3. using namespace std;
  4.  
  5. int main() {
  6.     // ------------------------------------------
  7.     // work with integers
  8.     // ------------------------------------------
  9.     Vector<int> v(100);
  10.    
  11.     for (int i=0; i<v.get_size(); ++i) {
  12.         v[i] = i+1;
  13.     }
  14.    
  15.     Vector<int> w(v);
  16.    
  17.     int sumi = 0;
  18.     for (int i=0; i<v.get_size(); ++i) {
  19.         sumi += v[i];
  20.     }
  21.    
  22.     cout << "sum(int) = " << sumi << endl;
  23.  
  24.     // ------------------------------------------
  25.     // work with floats
  26.     // ------------------------------------------
  27.    
  28.     Vector<float> x(10), y(10), z(10);
  29.     for (int i=0; i<x.get_size(); ++i) {
  30.         x[i] = i * 2.0;
  31.         y[i] = i * 3.0;
  32.     }
  33.    
  34.     for (int i=0; i<x.get_size(); ++i) {
  35.         z[i] = x[i] + y[i];
  36.     }
  37.    
  38.     float sumf = 0.0;
  39.     for (int i=0; i<z.get_size(); ++i) {
  40.         sumf += z[i];
  41.     }
  42.    
  43.     cout << "sum(float) = " << sumf << endl;
  44.    
  45.     return 0;
  46. }
  47.  
  48.  
sum(int) = 5050
sum(float) = 225