Supposons que nous souhaitons définir une classe qui founit un buffer d'une taille prédéfinie :
#include<algorithm> class buffer { private: uint8_t m_buffer[128]; public: buffer(const buffer& anotherBuffer) { std::copy(anotherBuffer.m_buffer, anotherBuffer.m_buffer + 128, m_buffer); } int size() const { return 128; } buffer& operator = (const buffer& anotherBuffer) { std::copy(anotherBuffer.m_buffer, anotherBuffer.m_buffer + 128, m_buffer); return this; } operator const uint8_t*() const { return m_buffer; } operator uint8_t*() { return m_buffer; } uint8_t operator[](int anIndex) const { if(anIndex >= 128) throw std::out_of_range("index larger than buffer"); return m_buffer[anIndex]; } uint8_t& operator[](int anIndex) { if(anIndex >= 128) throw std::out_of_range("index larger than buffer"); return m_buffer[anIndex]; } };
Cette classe définit un buffer d'une taille de 128 octets. Elle redéfinit les opérateurs suivant :
operator[]
qui permet d'accéder en lecture et en écriture au buffer interne natif.operator uint8_t*()
qui permet de récupérer un pointeur autorisant la lecture ou l'écriture dans le buffer vu.operator =(const buffer&)
et buffer(const buffer&)
correspondant à l'opérateur de recopie.Nous voyons que si nous souhaitons faire un buffer d'une taille différente, nous devons définir une nouvelle classe dont la seule différence est la taille du buffer.
De même si nous souhaitons faire un buffer qui va stocker des éléments de base autres que des entiers 8 bits, par exemple des entiers 64 bits, il est nécessaire de réécrire une nouvelle classe en remplaçant uint8_t
par un type différent.
Dans un premier temps, nous allons paramétriser la classe buffer
par le type des éléments qui sont contenus dans le buffer
.
Pour ce faire, nous allons déclarer la classe buffer
comme étant un modéle de classe à l'instar des modèles de fonctions.
#include<algorithm> template<class itemT> class buffer { private: itemT m_buffer[128]; public: buffer(const buffer<itemT>& anotherBuffer) { std::copy(anotherBuffer.m_buffer, anotherBuffer.m_buffer + 128, m_buffer); } int size() const { return 128; } buffer& operator = (const buffer<itemT>& anotherBuffer) { std::copy(anotherBuffer.m_buffer, anotherBuffer.m_buffer + 128, m_buffer); return this; } operator const itemT*() const { return m_buffer; } operator itemT*() { return m_buffer; } itemT operator[](int anIndex) const { if(anIndex >= 128) throw std::out_of_range("index larger than buffer"); return m_buffer[anIndex]; } itemT& operator[](int anIndex) { if(anIndex >= 128) throw std::out_of_range("index larger than buffer"); return m_buffer[anIndex]; } };
La déclaration de la classe comme un modèle de classe s'effectue par :
template<class itemT> class buffer
qui indique que la classe est un template qui a un type paramètre qui est le type des éléments stockés dans le buffer, soit le type itemT
.
Désormais, pour utiliser la classe buffer
, il est nécessaire de spécifier le type de ses éléments. Ainsi, quand je souhaite créer un buffer de caractères, il suffit d'écrire buffer<char>
:
void funct() { buffer<char> bufferOfCharacters; bufferOfCharacters[0] = 't'; bufferOfCharacters[1] = 'e'; bufferOfCharacters[2] = 's'; bufferOfCharacters[3] = 't'; std::string token((const char*)bufferOfCharacters, (const char*)bufferOfCharacters + 4); std::cout << token << std::endl; }
Cette fonction crée un buffer de 128 caractères dénommé bufferOfCharacters
, stocke dans le buffer les caractères 't', 'e', 's', 't', copie ces 4 caractères dans un objet string
et affiche le contenu de l'objet string
sur la console.
Il est possible de paramétrer des classes avec plus d'un type. Par exemple, la déclaration de la classe std::basic_string de la STL définit trois paramètres de type charT
, traits
et allocator
comme suit :
template< class charT, class traits = std::char_traits<charT>, class allocator = std::allocator<charT> class basic_string;
Il est intéressant de constater qu'il est possible de définir un paramètre de type par défaut. Ainsi, écrire :
std::basic_string<char> myString;
est équivalent à écrire :
std::basic_string<char, std::char_traits, std::allocator<charT>> myString;
Si nous ne spécifions pas les types traits
et allocator
, le compilateur utilise les types par défaut.
Nous souhaitons pouvoir choisir la taille du buffer. Pour ce faire, nous allons définir un modèle de classe qui est paramétrisé par la taille que nous souhaitons allouer au buffer.
#include<algorithm> template<class itemT, int bufferSize = 128> class buffer { private: itemT m_buffer[bufferSize]; public: buffer(const buffer<itemT, bufferSize>& anotherBuffer) { std::copy(anotherBuffer.m_buffer, anotherBuffer.m_buffer + bufferSize, m_buffer); } int size() const { return bufferSize; } buffer& operator = (const buffer<itemT, bufferSize>& anotherBuffer) { std::copy(anotherBuffer.m_buffer, anotherBuffer.m_buffer + bufferSize, m_buffer); return this; } operator const itemT*() const { return m_buffer; } operator itemT*() { return m_buffer; } itemT operator[](int anIndex) const { if(anIndex >= bufferSize) throw std::out_of_range("index larger than buffer"); return m_buffer[anIndex]; } itemT& operator[](int anIndex) { if(anIndex >= bufferSize) throw std::out_of_range("index larger than buffer"); return m_buffer[anIndex]; } };
Désormais, quand nous créons un buffer, nous pouvons spécifier la taille du buffer.
buffer<int, 64> bufferOfIntegers; // buffer contenant 64 entiers. buffer<char, 32> bufferOfChars; // buffer contenant 32 caractères. buffer<float> bufferOfFloats; // buffer contenant 128 (la valeur par défaut) nombres à virgule flottante.