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 :
- des fonctions génériques
- ou des classes génériques
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 :
#include <iostream>
#include <cstdlib>
using namespace std;
/**
* function that computes minimum of two values
*/
template<class T>
T minimum(T a, T b) {
return (a < b) ? a : b;
}
/**
* function that computes minimum of two values
* using a Predicate, here a function of the form
* bool Predicate(T a, T b)
* or
* bool Predicate(T& a, T& b)
*/
template<class T, class Predicate>
T minimum(T a, T b, Predicate p) {
return p(a, b) ? a : b;
}
/**
* class Person with name and age
*/
class Person {
protected:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {
}
string get_name() {
return name;
}
int get_age() {
return age;
}
/**
* redefinition of <<
*/
friend ostream& operator<<(ostream& out, const Person& p) {
out << p.name << ", " << p.age;
return out;
}
/**
* definition of less than operator two compare two person's age
*
*/
friend bool operator<(const Person& lhs, const Person& rhs) {
return lhs.age < rhs.age;
}
};
/**
* main function
*/
int main() {
// integer
int min_i = minimum(2, 1);
cout << "min int = " << min_i << endl;
// float
float min_f = minimum(3.0, 2.0);
cout << "min float = " << min_f << endl;
// Person
// will only work if the operator < has been defined
// for the class Person
Person riri("riri", 10);
Person fifi("fifi", 11);
Person p_min_age = minimum(riri, fifi);
cout << "min age = " << p_min_age << endl;
Person p_min_name = minimum(riri, fifi, [](Person& lhs, Person& rhs) {
return lhs.get_name() < rhs.get_name();
});
cout << "min name = " << p_min_name << endl;
return EXIT_SUCCESS;
}
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
#include <vector>
#include <list>
#include <iostream>
#include <cstdlib>
using namespace std;
/**
* function that prints the contents of a sequence
* container : vector, list, deque, ...
*/
template<typename Container>
void print(Container& c) {
auto it = c.begin();
if (c.size() > 0) {
cout << "[ " << *it++;
while (it != c.end()) {
cout << " " << *it;
++it;
}
cout << " ]" << endl;
} else {
cout << "<empty>" << endl;
}
}
/**
* main function
*/
int main() {
// print contents of a vector
vector<int> v = { 1, 8, 4, 3, -7, 6 };
print(v);
// print contents of a list
list<string> w = { "riri", "fifi", "loulou" };
print(w);
return EXIT_SUCCESS;
}
[ 1 8 4 3 -7 6 ]
[ riri fifi loulou ]
On dispose également de la possibilité de créer des template de template :
#include <vector>
#include <list>
#include <iostream>
#include <cstdlib>
using namespace std;
/**
* function that prints the contents of a sequence
* container : vector, list, deque, ...
* we use a template of template
*/
template< typename V,
template<typename T, typename A> class Container = std::vector>
void print(Container<V, std::allocator<V> >& c) {
auto it = c.begin();
if (c.size() > 0) {
cout << "[ " << *it++;
while (it != c.end()) {
cout << " " << *it;
++it;
}
cout << " ]" << endl;
} else {
cout << "<empty>" << endl;
}
}
/**
* main function
*/
int main() {
// print contents of a vector
vector<int> v = { 1, 8, 4, 3, -7, 6 };
print(v);
// print contents of a list
list<string> w = { "riri", "fifi", "loulou" };
print(w);
return EXIT_SUCCESS;
}
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 :
#ifndef VECTOR_H
#define VECTOR_H
/**
***********************************************
* Vector of elements
***********************************************
*/
template<class T>
class Vector {
// =============================================
// data members
// =============================================
protected:
// maximum number of elements
int max_size;
// dynamic array of elements
T *elements;
// =============================================
// methods
// =============================================
public:
// -----------------------------------------
// default constructor with 10 elements
// -----------------------------------------
Vector(int sz = 10) {
max_size = sz;
elements = new T [ max_size ];
}
// -----------------------------------------
// copy constructor
// -----------------------------------------
Vector(Vector& v) {
max_size = v.max_size;
elements = new T [ max_size ];
for (int i=0; i<max_size; ++i) {
elements[i] = v.elements[i];
}
}
// -----------------------------------------
// assignment operator
// -----------------------------------------
Vector& operator=(Vector& v) {
if (&v != this) {
delete [] elements;
max_size = v.max_size;
elements = new T [ max_size ];
for (int i=0; i<max_size; ++i) {
elements[i] = v.elements[i];
}
}
return *this;
}
// -----------------------------------------
// destructor
// -----------------------------------------
~Vector() {
delete [] elements;
}
// -----------------------------------------
// accessor
// -----------------------------------------
T& operator[](int n) {
return elements[n];
}
// -----------------------------------------
// getter
// -----------------------------------------
int get_size() {
return max_size;
}
};
#endif
#include "generic_vector.h"
#include <iostream>
using namespace std;
int main() {
// ------------------------------------------
// work with integers
// ------------------------------------------
Vector<int> v(100);
for (int i=0; i<v.get_size(); ++i) {
v[i] = i+1;
}
Vector<int> w(v);
int sumi = 0;
for (int i=0; i<v.get_size(); ++i) {
sumi += v[i];
}
cout << "sum(int) = " << sumi << endl;
// ------------------------------------------
// work with floats
// ------------------------------------------
Vector<float> x(10), y(10), z(10);
for (int i=0; i<x.get_size(); ++i) {
x[i] = i * 2.0;
y[i] = i * 3.0;
}
for (int i=0; i<x.get_size(); ++i) {
z[i] = x[i] + y[i];
}
float sumf = 0.0;
for (int i=0; i<z.get_size(); ++i) {
sumf += z[i];
}
cout << "sum(float) = " << sumf << endl;
return 0;
}
sum(int) = 5050
sum(float) = 225