Tableau dynamique – Implémentation par injection de parent

L'approche par injection de parents est fortement similaire à la stratégie collaborative et générique, reposant sur les mêmes concepts mais exprimés de manière différente.

Imaginons un type (que j'ai nommé Croisseur ici, mais entendons-nous qu'il s'agit d'un nom trouvé tard en fin de soirée et que vous êtes les bienvenu(e)s de me proposer mieux) capable d'exprimer certaines caractéristiques propres à un outil susceptible de prendre en charge une stratégie de croissance. Ici, Croisseur ne sera pas polymorphique et définira surtout certains types autour des concepts de valeur (T), de capacité (SzT) et de pointeur (PtrT), dans le but d'alléger la syntaxe de la méthode grow() de ses enfants.

Chaque type de Croisseur (par exemple CroisseurTypique) pourra déterminer une stratégie de croissance qui lui est propre. Toutefois, aucun polymorphisme ne sera requis, sinon une certaine conformité sur le plan de la signature de la méthode grow(). des divers types de Croisseur.

Un tableau dynamique sera un cas particulier (par héritage privé!) de Croisseur, quel qu'il soit mais déterminé à la compilation pour chaque instance de tableau dynamique. Ce parent injecté par le code client prendra en charge la croissance de son enfant. Le Croisseur en question sera d'un type déterminé par les paramètres d'instanciation du template Tableau, ce qui fait que chaque instance de Tableau sera susceptible d'avoir sa propre stratégie de croissance. Chaque Tableau reposera sur un type de valeur (le type T) et sur un type de Croisseur, et cette combinaison entraînera à la fois la génération de l'instance elle-même et, au besoin, de son type.

Cette stratégie est très souple, mais moins que la stratégie collaborative polymorphique : les concepts de tableau et de politique de croissance sont découplés, réduits à leur plus simple expression (celle de service de croissance sans état), et la politique de croissance peut changer d'une instance de Tableau à l'autre, mais ne pourra pas changer pour un Tableau donné au cours de son existence. Puisque aucun pointeur vers un Croisseur n'est impliqué, le nombre d'indirections est fortement réduit et le code sera en général beaucoup plus rapide du fait qu'il sera sujet à une plus grande quantité d'optimisations de la part du compilateur.

Le couplage entre deux classes est plus fort en situation d'héritage qu'en situation de composition. Cependant, dans ce cas-ci, l'héritage est un détail d'implémentation de l'enfant puisqu'il s'agit d'héritage privé, à l'insu du code client. Ceci permet au parent d'exposer ses services par une méthode protégée, utile seulement pour l'enfant.

Remarquez l'implémentation de grow() dans Tableau : elle invoque GrowthPol::grow() avec des paramètres. Pourquoi n'invoque-t-elle pas plutôt simplement grow() avec des paramètres? Après tout, les signatures des méthodes grow() de l'enfant et du parent sont différentes, non?

#ifndef TABLEAU_H
#define TABLEAU_H
 
#include <algorithm>
#include <initializer_list>

class HorsBornes {};

template <class T, class SzT, class PtrT>
   struct Croisseur { // hum...
      using value_type = T;
      using size_type = SzT;
      using pointer = PtrT;
      using ptrref_t = pointer&;
   };

template <class T, class SzT = std::size_t, class PtrT = T*>
   class CroisseurTypique : public Croisseur<T, SzT, PtrT> {// <-- ICI
   protected:
      size_type grow(ptrref_t tab, size_type old_cap) const { // <-- ICI
         using std::copy;
         const size_type TAILLE_DEFAUT = 16;
         const float FACTEUR_CROISSANCE = 1.5f;
         const auto nouv_cap = static_cast<size_type>(old_cap? old_cap * FACTEUR_CROISSANCE : TAILLE_DEFAUT);
         pointer p = new value_type[nouv_cap];
         try {
            copy(tab, tab+old_cap, p);
            delete [] tab;
            tab = p;
         } catch(...) {
            delete [] p;
            throw;
         }
         return nouv_cap;
      }
   };

template <class T, class GrowthPol = CroisseurTypique<T, int>>
   class Tableau : GrowthPol {
   public:
      using value_type = T;
      using size_type = int;
      using reference = value_type&;
      using const_reference = const value_type&;
      using pointer = value_type*;
      using const_pointer = const value_type*;
      using iterator = pointer;
      using const_iterator = const_pointer;
   private:
      //
      //
      //
      pointer elems;
      size_type nelems,
                cap;
   public:
      bool empty() const noexcept {
         return !size();
      }
      size_type size() const noexcept {
         return nelems;
      }
      size_type capacity() const noexcept {
         return cap;
      }
   private:
      bool full() const noexcept {
         return size() == capacity();
      }
   public:
      iterator begin() noexcept {
         return elems;
      }
      const_iterator begin() const noexcept {
         return elems;
      }
      const_iterator cbegin() const noexcept {
         return begin();
      }
      iterator end() noexcept {
         return begin() + size();
      }
      const_iterator end() const noexcept {
         return begin() + size();
      }
      const_iterator cend() const noexcept {
         return end();
      }
      //
      // constructeur par défaut
      //
      Tableau() : GrowthPol{}, nelems{}, elems{}, cap{} { // <-- ICI  
      }
      //
      // constructeur paramétrique
      //
      Tableau(size_type n, const value_type &val) : GrowthPol{}, nelems{ n }, elems{ new value_type[n] }, cap{ n } { // <-- ICI
         try {
            std::fill(begin(), end(), val);
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur de copie
      //
      Tableau(const Tableau &autre) // <-- ICI
         : GrowthPol{ autre }, nelems{ autre.size() }, elems{ new value_type[autre.size()] }, cap{ autre.size() }
      {
         try {
            std::copy(autre.begin(), autre.end(), begin());
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur par liste d'initialisation
      //
      Tableau(std::initializer_list<value_type> src) // <-- ICI
         : GrowthPol{}, nelems{ src.size() }, cap{ src.size() }, elems{ new value_type[src.size()] }
      {
         try {
            std::copy(src.begin(), src.end(), begin());
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur de séquence
      //
      template <class It>
         Tableau(It debut, It fin) : GrowthPol{}, nelems{ std::distance(debut, fin) }, croisseur{ strategie } { // <-- ICI
            cap = size();
            elems = new value_type[capacity()];
            try {
               std::copy(debut, fin, begin());
            } catch(...) {
               delete [] elems;
               throw;
            }
         }
      //
      // constructeur de conversion
      //
      template <class U>
         Tableau(const Tableau<U> &autre) // <-- ICI
            : GrowthPol{}, nelems{ autre.size() }, elems{ new value_type[autre.size()] }, cap{ autre.size() }
         {
            try {
               std::copy(autre.begin(), autre.end(), begin());
            } catch(...) {
               delete [] elems;
               throw;
            }
         }
      //
      // constructeur de mouvement
      //
      Tableau(Tableau &&autre) : GrowthPol{} { // <-- ICI
         elems = std::move(autre.elems);
         nelems = std::move(autre.nelems);
         cap = std::move(autre.cap);
         autre.elems = {};
         autre.nelems = {};
         autre.cap = {};
      }
      ~Tableau() {
         delete [] elems;
      }
      void swap(Tableau &autre) {
         using std::swap;
         swap(elems, autre.elems);
         swap(nelems, autre.nelems);
         swap(cap, autre.cap);
      }
      //
      // affectation
      //
      Tableau& operator=(const Tableau &autre) {
         Tableau{ autre }.swap(*this);
         return *this;
      }
      //
      // affectation covariante
      //
      template <class U>
         Tableau& operator=(const Tableau<U> &autre) {
            Tableau{ autre }.swap(*this);
            return *this;
         }
      //
      // affectation par mouvement
      //
      Tableau& operator=(Tableau &&autre) {
         delete [] elems;
         elems = std::move(autre.elems);
         nelems = std::move(autre.nelems);
         cap = std::move(autre.cap);
         autre.elems = {};
         autre.nelems = {};
         autre.cap = {};
         return *this;
      }
   private: // <-- ICI
      //
      //
      //
      void grow() {
         cap = GrowthPol::grow(elems, capacity()); // <-- ICI
      }
   public:
      void push_back(const_reference val) {
         if (full())
            grow();
         elems[size()] = val;
         ++nelems;
      }
      value_type at(size_type n) const {
         if (size() < n) throw HorsBornes{};
         return elems[n];
      }
      reference at(size_type n) {
         if (size() < n) throw HorsBornes{};
         return elems[n];
      }
      value_type operator[](size_type n) const {
         return elems[n];
      }
      reference operator[](size_type n) noexcept {
         return elems[n];
      }
      bool operator==(const Tableau &autre) const {
         return size() == autre.size() &&
                std::equal(begin(), end(), autre.begin());
      }
      bool operator!=(const Tableau &autre) const {
         return !(*this == autre);
      }
   };
 
#endif

Un programme de test suit. Ce programme ajoute N éléments au tableau, provoquant au passsage à plus d'une reprise l'activation de la stratégie de croissance, puis affiche les éléments sur la sortie standard.

#include "Tableau.h"
#include <iostream>
#include <algorithm>
#include <iterator>
int main() {
   using namespace std;
   using value_type = Tableau<int>::value_type;
   enum { N = 30 };
   Tableau<int> tab;
   for (int i = 0; i < N; ++i)
      tab.push_back(i);
   copy(begin(tab), end(tab), ostream_iterator<value_type>{cout, " "});
   cout << endl;
}

Que pensez-vous cette solution?


Valid XHTML 1.0 Transitional

CSS Valide !