Tableau dynamique – Implémentation locale

La plus simple implémentation possible d'une stratégie de croissance pour un tableau dynamique est sans doute de mettre en place le code associé à cette stratégie directement dans le tableau.

La version proposée ici est épurée. La méthode d'instance privée grow() implémente une stratégie de croissance du tableau contenant les données à proprement dit, et a directement accès aux attributs du tableau. L'encapsulation est donc très forte.

En retour, le couplage est aussi très fort, faisant partie de la classe Tableau de manière intrinsèque. Pour changer de stratégie de croissance, il faut écrire une toute nouvelle classe.

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

class HorsBornes {};
 
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;
   private:
      pointer elems {};
      size_type nelems {},
                cap {};
   public:
      [[nodiscard]] bool empty() const noexcept {
         return !size();
      }
      [[nodiscard]] size_type size() const noexcept {
         return nelems;
      }
      [[nodiscard]] size_type capacity() const noexcept {
         return cap;
      }
   private:
      [[nodiscard]] bool full() const noexcept {
         return size() == capacity();
      }
   public:
      [[nodiscard]] iterator begin() noexcept {
         return elems;
      }
      [[nodiscard]] const_iterator begin() const noexcept {
         return elems;
      }
      [[nodiscard]] const_iterator cbegin() const noexcept {
         return begin();
      }
      [[nodiscard]] iterator end() noexcept {
         return begin() + size();
      }
      [[nodiscard]] const_iterator end() const noexcept {
         return begin() + size();
      }
      [[nodiscard]] const_iterator cend() const noexcept {
         return end();
      }
      //
      // constructeur par défaut
      //
      Tableau() = default;
      //
      // constructeur paramétrique
      //
      Tableau(size_type n, const value_type &val) : nelems{ n }, elems{ new value_type[n] }, cap{ n } {
         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 {
            std::copy(autre.begin(), autre.end(), begin());
         } catch(...) {
            delete [] elems;
            throw;
         }
      }
      //
      // constructeur par liste d'initialisation
      //
      Tableau(std::initializer_list<value_type> src) : 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 (pourrait bénificier de enable_if ou
      // des concepts de C++ 20)
      //
      template <class It>
         Tableau(It debut, It fin) : nelems{ std::distance(debut, fin) } {
            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 {
               std::copy(autre.begin(), autre.end(), begin());
            } catch(...) {
               delete [] elems;
               throw;
            }
         }
      //
      // constructeur de mouvement
      //
      Tableau(Tableau &&autre)
         : nelems{ std::exchange(autre.nelems, 0) }, elems{ std::exchange(autre.elems, nullptr) }, cap{ std::exchange(autre.cap, 0) } {
      }
      ~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) {
         Tableau{ std::move(autre) }.swap(*this);
         return *this;
      }
   private:
      void grow() {
         const auto nouv_cap = capacity()? capacity() * 2 : 128;
         auto p = new value_type[nouv_cap];
         try {
            std::copy(begin(), end(), p);
            delete [] elems;
            elems = p;
            cap = nouv_cap;
         } catch(...) {
            delete [] p;
            throw;
         }
      }
   public:
      void push_back(const_reference val) {
         if (full())
            grow();
         elems[size()] = val;
         ++nelems;
      }
      [[nodiscard]] const_reference at(size_type n) const {
         if (size() < n) throw HorsBornes{};
         return elems[n];
      }
      [[nodiscard]] reference at(size_type n) {
         if (size() < n) throw HorsBornes{};
         return elems[n];
      }
      [[nodiscard]] const_reference operator[](size_type n) const {
         return elems[n];
      }
      [[nodiscard]] reference operator[](size_type n) noexcept {
         return elems[n];
      }
      [[nodiscard]] bool operator==(const Tableau &tab) const {
         return size() == tab.size() &&
                std::equal(begin(), end(), tab.begin());
      }
      [[nodiscard]] bool operator!=(const Tableau &tab) const {
         return !(*this == tab);
      }
   };
 
#endif

Un programme de test suit. Ce programme ajoute N éléments au tableau, provoquant au passage à 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>
int main() {
   using namespace std;
   enum { N = 1'000 };
   Tableau<int> tab;
   for (int i = 0; i < N; ++i)
      tab.push_back(i);
   for(auto n : tab) cout << n << ' ';
   cout << endl;
}

Que pensez-vous cette « solution »?


Valid XHTML 1.0 Transitional

CSS Valide !