Tableau dynamique – Implémentation par mixin

Allons-y maintenant pour un oiseau un peu différent : une implémentation par mixin. Notez que ce mot porte plusieurs sens, et que l'implémentation ici n'est pas idiomatique de C++. Je la présente pour titiller vos neurones et faire réfléchir.

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_croisseur_typique() 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.

Voir https://wandbox.org/permlink/FRgmro5fCGn1eYtE

À 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_croisseur_typique().

#ifndef TABLEAU_H
#define TABLEAU_H

#include <algorithm>
#include <initializer_list>
#include <utility>
#include <memory>

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

template<class T, class SzT>
   struct AxiomeCroisseur { // hum...
      using value_type = T;
      using size_type = SzT;
      using pointer = T*;
      virtual std::pair<pointer, size_type> grow(pointer, size_type) const = 0;
      virtual size_type compute_new_cap(size_type) const = 0;
      virtual ~AxiomeCroisseur() = default;
   };

template <class T, class SzT = std::size_t>
   struct TheoremeCroisseur : virtual AxiomeCroisseur<T, SzT> {
      using size_type = typename AxiomeCroisseur<T, SzT>::size_type;
      using pointer = typename AxiomeCroisseur<T, SzT>::pointer;
      using value_type = typename AxiomeCroisseur<T, SzT>::value_type;
      virtual TheoremeCroisseur* cloner() const = 0;
      std::pair<pointer, size_type> grow(pointer old_p, size_type old_cap) const override {
         using std::copy;
         const auto nouv_cap = this->compute_new_cap(old_cap); // <-- Sibling call (this est requis)
         auto p = new value_type[nouv_cap];
         try {
            copy(old_p, old_p+old_cap, p);
         } catch(...) {
            delete [] p;
            throw;
         }
         return { p, nouv_cap };
      }
   };

template <class T, class SzT = std::size_t>
   struct ModeleCroisseurTypique : virtual AxiomeCroisseur<T, SzT> {
      using size_type = typename AxiomeCroisseur<T, SzT>::size_type;
      size_type compute_new_cap(size_type old_cap) const override { // <-- 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>
   auto creer_croisseur_typique() {
      class Mixin : public TheoremeCroisseur<T, SzT>, ModeleCroisseurTypique <T, SzT> {
         Mixin* cloner() const {
            return new Mixin{ *this };
         }
      };
      return std::make_unique<Mixin>();
   }

template <class T>
   class Tableau {
   public:
      // using...
      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:
      // empty(), size(), capacity(), full()
   public:
      auto clone_growth_strategy() const {
         return croisseur ? std::unique_ptr<growth_pol_t>{ croisseur->cloner() } : std::unique_ptr<growth_pol_t>{};
      }
      // begin(), cbegin(), end(), cend()
      //
      // constructeur par défaut
      //
      Tableau(std::unique_ptr<growth_pol_t> strategie = {}) noexcept : croisseur { std::move(strategie) } {
      }
      //
      // constructeur paramétrique
      //
      Tableau(size_type n, const_reference val, std::unique_ptr<growth_pol_t> strategie = {})
         : nelems{ n }, elems{ new value_type[n] }, cap{ n }, croisseur{ std::move(strategie) } {
         try {
            std::fill(begin(), end(), val);
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur de copie
      //
      Tableau(const Tableau &autre)
         : nelems{ autre.size() }, elems{ new value_type[autre.size()] }, cap{ autre.size() } {
         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 = {})
         : nelems{ src.size() }, cap{ src.size() }, elems{ new value_type[src.size()] }, croisseur{ std::move(strategie) } {
         try {
            std::copy(src.begin(), src.end(), begin());
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur de séquence (pourrait bénificier de enable_if ou
      // des concepts de C++ 20)
      //
      template <class It>
         Tableau(It debut, It fin, std::unique_ptr<growth_pol_t> strategie = {})
            : nelems{ std::distance(debut, fin) }, croisseur{ std::move(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> &autre)
            : nelems{ autre.size() }, elems{ new value_type[autre.size()] }, cap{ autre.size() } {
            try {
               croisseur = autre.clone_growth_strategy();
               std::copy(autre.begin(), autre.end(), begin());
            } catch(...) {
               delete [] elems;
               throw;
            }
         }
      //
      // constructeur de mouvement
      //
      Tableau(Tableau &&autre)
         : elems { std::exchange(autre.elems, nullptr) },
           nelems { std::exchange(autre.nelems, 0) },
           cap { std::exchange(autre.cap, 0) },
           croisseur { std::move(autre.croisseur) } {
      }
      ~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, affectation covariante
      //
      // affectation par mouvement
      //
   private:
      void grow() {
         if (!croisseur) throw IncapableDeCroitre{};
         auto [nouv_p, nouv_cap] = croisseur->grow(elems, capacity());
         delete [] elems;
         elems = nouv_p;
         cap = nouv_cap;
      }
   public:
      // push_back(), at(), operator[]
      // operator==, operator!=
   };
 
#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;
   enum { N = 1'000 };
   Tableau<int> tab( creer_croisseur_typique<int,size_t>() );
   for (int i = 0; i < N; ++i)
      tab.push_back(i);
   for(auto n : tab) cout << n << ' ';
   cout << endl;
}

Que pensez-vous de cette solution?


Valid XHTML 1.0 Transitional

CSS Valide !