Tableau dynamique – Implémentation par mixin

Allons-y maintenant pour un oiseau un peu différent : une implémentation par mixin.

La classe axiome se nomme ici AxiomeCroisseur et détermine les paramètres propres aux accès primitifs sur les attributs d'un tableau dynamique. Pour certains mixin, cette classe sera mince et légère, mais dans ce cas-ci elle sera un peu plus complexe à cause de l'interface attendue pour un conteneur un tant soit peu standard (itérateurs et autres).

L'interface TheoremeCroisseur permet de gérer la croissance de la capacité d'un tableau. Elle implémente une abstraction polymorphique de croissance nommée grow() qui fait le travail... dans la mesure où une méthode compute_new_cap() serait implémentée. C'est par cette interface que le client (le Tableau) gérera sa croissance. Pour permettre au Tableau de le dupliquer, il doit être clonable, mais ne peut implémenter le clonage sur lui-même, étant abstrait.

Chaque classe modèle, dont celle nommée ici ModeleCroisseurTypique, implémente une stratégie de croissance qui lui est propre. Il peut y avoir un nombre arbitrairement grand de classes modèles.

Le mixin est une classe (idéalement anonyme, mais le fait que cette proposition d'implémentation repose sur un type générique nous empêche d'aller à ce niveau d'abstraction) qui combine diverses stratégies par héritage multiple virtuel. Notez que la classe terminale de la hiérarchie, Mixin, est responsable d'initialiser ses parents immédiats mais aussi d'initialiser ses ancêtres fusionnels, ce qui peut être utile dans des cas plus complexes. Il doit implémenter le clonage.

La fonction creer_croisseurtypique() est un générateur créant sur demande un nouveau mixin reposant sur une stratégie de croissance typique, et la classe d'enrobage Tableau encapsule un tel mixin pour en faciliter l'utilisation.

À titre d'exercice, remodelez Tableau pour que cette classe prenne un deuxième paramètre générique indiquant la fonction génératrice de mixin à utiliser plutôt que d'utiliser systématiquement creer_croisseurtypique().

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

class HorsBornes {};
class IncapableDeCroitre{}; // <-- ICI

template<class T, class SzT, class PtrT>
   struct AxiomeCroisseur { // hum...
      using value_type = T;
      using size_type = SzT;
      using pointer = PtrT;
      using ptrref_t = pointer&;
      virtual size_type grow(ptrref_t tab, size_type new_cap) const = 0;
      virtual size_type compute_new_cap(size_type new_cap) const = 0;
      virtual ~AxiomeCroisseur() = default;
   };

template <class T, class SzT = std::size_t, class PtrT = T*>
   struct TheoremeCroisseur : virtual AxiomeCroisseur<T, SzT, PtrT> {
      virtual TheoremeCroisseur* cloner() const = 0;
      size_type grow(ptrref_t tab, size_type old_cap) const { // <-- ICI
         using std::copy;
         const size_type nouv_cap = compute_new_cap(old_cap); // <-- Sibling call
         auto 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 SzT = std::size_t, class PtrT = T*>
   struct ModeleCroisseurTypique : virtual AxiomeCroisseur<T, SzT, PtrT> {
      size_type compute_new_cap(size_type old_cap) const { // <-- ICI
         const size_type TAILLE_DEFAUT = 128;
         const float FACTEUR_CROISSANCE = 1.5f;
         return static_cast<size_type>(old_cap? old_cap * FACTEUR_CROISSANCE : TAILLE_DEFAUT);
      }
   };

template <class T, class SzT, class PtrT>
   TheoremeCroisseur<T, SzT, PtrT>* creer_croisseurtypique() {
      class Mixin : public TheoremeCroisseur<T, SzT, PtrT>, public ModeleCroisseurTypique < T, SzT, PtrT > {
         Mixin* cloner() const {
            return new Mixin{ *this };
         }
      };
      return new Mixin;
   }

template <class T>
   class Tableau {
   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;
      using growth_pol_t = TheoremeCroisseur<value_type,size_type>; // <-- ICI
   private: // <-- ICI
      //
      //
      //
      std::unique_ptr<growth_pol_t> croisseur;
      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:
      std::unique_ptr<growth_pol_t> clone_growth_strategy() const {
         return croisseur ? std::unique_ptr<growth_pol_t>{ croisseur->cloner() } : std::unique_ptr<growth_pol_t>{};
      }
      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(std::unique_ptr<growth_pol_t> strategie = {}) noexcept // <-- ICI
         : nelems{}, elems{}, cap{}, croisseur{ strategie }
      {
      }
      //
      // constructeur paramétrique
      //
      Tableau(size_type n, const value_type &val, std::unique_ptr<growth_pol_t> strategie = {}) // <-- ICI
         : nelems{ n }, elems{ new value_type[n] }, cap{ n }, croisseur{ strategie }
      {
         try {
            std::fill(begin(), end(), val);
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur de copie
      //
      Tableau(const Tableau &autre) // <-- ICI
         : nelems{ autre.size() }, elems{ new value_type[autre.size()] }, cap{ autre.size() }, croisseur{}
      {
         try {
            croisseur = autre.clone_growth_strategy();
            std::copy(autre.begin(), autre.end(), begin());
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur par liste d'initialisation
      //
      Tableau(std::initializer_list<value_type> src, std::unique_ptr<growth_pol_t> strategie = {}) // <-- ICI
         : nelems{ src.size() }, cap{ src.size() }, elems{ new value_type[src.size()] }, croisseur{ strategie }
      {
         try {
            std::copy(src.begin(), src.end(), begin());
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur de séquence
      //
      template <class It>
         Tableau(It debut, It fin, std::unique_ptr<growth_pol_t> strategie = {}) // <-- ICI
            : nelems{ std::distance(debut, fin) }, croisseur{ strategie }
         {
            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> &v) // <-- ICI
            : nelems{ autre.size() }, elems{ new value_type[autre.size()] }, cap{ autre.size() }, croisseur{}
         {
            try {
               croisseur = autre.clone_growth_strategy();
               std::copy(autre.begin(), autre.end(), begin());
            } catch(...) {
               delete [] elems;
               throw;
            }
         }
      //
      // constructeur de mouvement
      //
      Tableau(Tableau &&autre) { // <-- ICI
         elems = std::move(autre.elems);
         nelems = std::move(autre.nelems);
         cap = std::move(autre.cap);
         croisseur = std::move(autre.croisseur);
         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);
         swap(croisseur, autre.croisseur);
      }
      //
      // 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);
         croisseur = std::move(v.croisseur);
         autre.elems = {};
         autre.nelems = {};
         v.cap = {};
         return *this;
      }
   private: // <-- ICI
      //
      //
      //
      void grow() {
         if (!croisseur) throw IncapableDeCroitre{};
         cap = croisseur->grow(elems, capacity());
      }
   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 passage 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 size_type = Tableau<int>::size_type;
   using value_type = Tableau<int>::value_type;
   enum { N = 30 };
   Tableau<int> tab( creer_croisseurtypique<int,size_type,int*>() );
   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 de cette solution?


Valid XHTML 1.0 Transitional

CSS Valide !