Partage
  • Partager sur Facebook
  • Partager sur Twitter

Surcharge opérateur [][]

Accès aux éléments d'une matrice

Sujet résolu
    16 novembre 2020 à 16:18:05

    Bonjour, il y a peu de temps j'avais reçu beaucoup de conseil techniques sur les bonnes méthodes pour écrire du C++ moderne. On m'avait entre autre vivement conseillé de me pencher sur le RAII, ce que je suis en train de faire avec la page de cours de gbdivers sur ce sujet : 

    http://guillaume.belz.free.fr/doku.php?id=pourquoi_le_raii_est_fondamental_en_c

    Afin de mieux assimiler le concept, je me suis lancé un petit défi. Ecrire une classe Matrice qui me permettrait de créer une matrice d'entier (je verrai après pour y mêler les templates, une chose à la fois) de n'importe quelle taille, l'afficher et faire quelques opérations dessus.

    La contrainte que je me donne est de faire cette classe en respectant le RAII mais sans utiliser les conteneurs de la STL qui sont déjà RAII.

    Ma matrice est représentée par un double pointeur sur un int.

    J'ai su surcharger l'opérateur [] pour récupérer un pointeur sur un tableau d'entier mais là je suis complèteent bloqué. J'avoue que le titre est un petit peu clickbait, dans mes recherches j'ai vu que [][] n'était pas un opérateur surchargeable comme l'est [] mais je ne vois pas comment implémenter la notation [][] pour accéder à un élément précis de ma matrice.

    Voici le code que j'ai :

    Matrice .h

    #ifndef MATRICE_H
    #define MATRICE_H
    
    class Matrice
    {
    public:
    	Matrice(const std::size_t rows, const std::size_t cols);
    	~Matrice();
    
    	void initialise();
    	void display();
    
    	// ---- Operator overloading ----
    	int* operator[](const std::size_t row);
    	int const* operator[](const std::size_t row) const;
    
    private:
    	int** matrix;
    
    	std::size_t _rows;
    	std::size_t _cols;
    
    };
    #endif

    Matrice.cpp

    #include <iostream>
    #include <assert.h>
    
    #include "Matrice.h"
    
    Matrice::Matrice(const std::size_t rows, const std::size_t cols)
    {
    	_rows = rows;
    	_cols = cols;
    
    	matrix = new int*[rows];
    	for (std::size_t i = 0; i < rows; i++) {
    		*(matrix + i) = new int[cols];
    	}
    }
    
    Matrice::~Matrice()
    {
    	// Faut-il delete les tableaux un à un comme ceci ou on peut faire delete[][] matrix directement ??
    	for (std::size_t i = 0; i < _rows; i++)
    		delete[] *(matrix + i);
    	delete[] matrix;
    }
    
    void Matrice::initialise()
    {
    	// Remplis chaque case de ma matrice avec une valeur entière.
    	for (std::size_t i = 0; i < _rows; i++) {
    		for (std::size_t j = 0; j < _cols; j++) {
    			*(*(matrix + i) + j) = static_cast<int>(2 * i + j);
    		}
    	}
    }
    
    void Matrice::display()
    {
    	for (std::size_t i = 0; i < _rows; i++) {
    		for (std::size_t j = 0; j < _cols; j++) {
    			std::cout << *(*(matrix + i) + j) << "  ";
    		}
    		std::cout << '\n';
    	}
    }
    
    
    // ---- Operator overloading ----
    int* Matrice::operator[](const std::size_t row)
    {
    	assert(row < _rows); // Sinon on a un out_of_range
    	return *(matrix + row);;
    }
    
    int const* Matrice::operator[](const std::size_t row) const
    {
    	assert(row < _rows);
    	return *(matrix + row);;
    }

    Et le main qui pour le moment test juste l'initialisation d'une matrice et son affichage :

    // Creation d'une matrice RAII
    
    #include <iostream>
    #include <array>
    
    #include "Matrice.h"
    
    int main()
    {
    	Matrice mat = Matrice(4, 3);
    
    	mat.initialise();
    	mat.display();
    	/*
    	affiche :
    	0  1  2
    	2  3  4
    	4  5  6
    	6  7  8
    	*/
    
    }

    Je n'ai pas de morceaux de code à vous proposer pour l'implémentation de [][] car je n'ai vraiment eu aucune idée, est ce que vous pourriez me mettre sur la piste svp ?

    Si à la lecture de mon morceau de code, certains ont des remarques à faire je vous écoute avec grand plaisir même si ça n'a rien à voir avec la question du [][]

    Merci d'avance :)



    • Partager sur Facebook
    • Partager sur Twitter
      16 novembre 2020 à 17:06:48

      Salut. La linéarisation de tableaux 2D fera ton bonheur ;)

      Tu peux stocker dans ta classe Matrix un tableau à une seule dimension. Le nombre de cases de ce tableau sera le nombre de lignes de ta matrice multiplié par le nombre de colonnes.

      Maintenant si tu souhaites accéder à l'élément (i,j) de ta matrice, quelle serait son indice dans ton tableau à une dimension ?

      Si tu alignais toutes les cases de ce tableau 2D dans un tableau à une dimension, sous la forme [8,6,5,4,2...,2], comment ferais-tu pour accéder au tout premier élément ? Puis au second ? Et enfin de manière général à l'élément (i,j) ?

      Tu peux essayer d'implémenter ça, tu gagneras en performance et ce sera beaucoup plus simple et agréable à utiliser qu'un gros tableau 2D.

      Si néanmoins tu persistes à vouloir utiliser un tableau 2D, ne suffirait-il pas d'une surcharge de l'opérateur () ?

      int& Matrice::operator()(std::size_t line, std::size_t row)
      {
          /* On imaginerait des exceptions pour la validité de l'opération */
      
          return matrix[line][row];
      }

      Ah oui aussi, pourquoi renvoyer un pointeur et pas une référence (respectivement constante, pour la lecture seule) ?

      • Partager sur Facebook
      • Partager sur Twitter
        16 novembre 2020 à 17:24:53

        Si tu veux être strict d'un point de vue RAII, l'allocation dynamique à plusieurs dimensions devient vite compliquée quand on veut faire cela à la main, cf la FAQ de dvpz: https://cpp.developpez.com/faq/cpp/?page=Gestion-dynamique-de-la-memoire#Comment-allouer-dynamiquement-un-tableau-a-plusieurs-dimensions

        De plus, cela n'est pas efficace d'un point de vue performances (plein d'allocs, blobs éparpillés qui pourrissent l'utilisation des caches...), du coup on évite et on linéarise comme l'a dit AmirProg.

        Maintenant, coté syntaxe, `[][]`, c'est compliqué à émuler sans introduire des couches d'indirection. Résultat...? On ne fait pas comme ça, on surcharge à la place l'opérateur () (cf réponse d'AmirProg de nouveau). (Techniquement, pour [][], on ne définit que [] dans la matrice, et cela renvoie quelque chose qui fournira son propre opérateur [] -- soit une classe de vue sur vecteur, soit directement un pointeur si on ne veut pas s’enquiquiner à renforcer le typage de notre système)

        (Pour info, il y a eu une proposition d'inhiber l'opérateur virgule dans le contexte de l'opérateur [] afin de pouvoir utiliser `matrice[i, j]`  au lieu de `matrice(i, j)` -- pas avant le C++23 si c'est accepté)

        PS: gdivers recommande plutôt de se rediriger vers le cours de Zeste de Savoir qui est maintenu et auquel on sommes plusieurs à participer, comme relecteurs.

        -
        Edité par lmghs 16 novembre 2020 à 17:25:24

        • Partager sur Facebook
        • Partager sur Twitter
        C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
          16 novembre 2020 à 17:30:49

          Salut merci de ta réponse ! 

          J'ai oublié de préciser dans mon premier post que j'ai volontairement fais le choix de ne pas linéariser mon tableau afin de me compliquer un petit peu plus la tâche sur la gestion des pointeurs qui me pose encore soucis. Faut vraiment voir ça comme une démarche d'exercice pour de l'apprentissage pur, pas particulièrement une recherche de la meilleure structure, la plus simple à coder et la plus performante

          Ta proposition de surcharge de l'opérateur () fonctionne très bien, je n'avais pas pensé qu'en déclarant un tableau comme en C on avait directement accès a ses éléments avec []

          Mais je vais être embêtant, je trouve pas ça intuitif. En tant que bête utilisteur de ma classe Matrice, c'est un objet mathématique qui représente un tableau à N dimmensions. Intuitivement j'aurais envie d'utiliser cette classe comme un tableau à N dimensions, donc avec N crochets pour accéder à un élément [][] ... []. J'aimerais bien réussir à l'implémenter sans utiliser une porte dérobée 

          Pour ta dernière question, vu que je représente ma matrice avec un poiteur double, ça me semblait logique de renvoyer un pointeur. Si je voulais renvoyer une référence vers un des tableau 1D de la matrice ça donnerait ça 

          int& Matrice::operator[](const std::size_t row)
          {
          	assert(row < _rows);
          	return (matrix + row);;
          }

          Je connais la différence entre un pointeur et une référence mais là j'ai vraiment l'impression que c'est un détail d'implémentation qui revient assez rapidement au même. Je me trompe ?

          EDIT pour Imghs:

          Merci de ta réponse, qu'est ce que tu appelles "des couches d'indirections" stp ?

          Si je comprend bien, si j'avais représenté ma matrice avec par exemple un tableau de vector (oui le mieux est un vector de vector mais c'est pour rester dans mon délire de surcharge de []) on pourrait avoir un truc du genre (écrit en speed sans tester) :

          // matrice .h
          
          public:
              std::vector<int>& operator[](const std::size_t row)
              {
                  assert(row < _rows); // Sinon on a un out_of_range
          	return (matrix + row);
              }
          
          private:
              std::vector<int>* matrix;


          Ce qui renverrait donc un vector, qui lui supporte déjà l'opérateur [] donc dans le main je pourrais utiliser [0][0] pour accéder au premier élément du premier vector qui compose ma matrice ?

          Sans utiliser de conteneurs de la STL j'ai pas trop le choix et il faut linéariser le tableau ?



          -
          Edité par ThibaultVnt 16 novembre 2020 à 17:42:27

          • Partager sur Facebook
          • Partager sur Twitter
            16 novembre 2020 à 18:20:53

            Si tu renvoies un pointeur vers le début de la ligne, ce pointeur supporte []... -- et renvoyer un référence depuis [] dans une optique [][] n'as pas de sens.

            Mais c'est une vue qu'il faut renvoyer pas un std::vector qui aura sa propre mémoire indépendante. Un std::span sera mieux -- aucune excuse pour ne pas commencer à utiliser des versions adaptées à la version courante du langage employée (11, 14...).

            • Partager sur Facebook
            • Partager sur Twitter
            C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
              16 novembre 2020 à 18:25:08

              lmghs a écrit:

              PS: gdivers recommande plutôt de se rediriger vers le cours de Zeste de Savoir qui est maintenu et auquel on sommes plusieurs à participer, comme relecteurs.

              (il y a souvent confusion entre mon cours et les articles hors cours sur mon site. C'est un article indépendant de mon cours. Et de toute façon, mon cours n'est plus du tout en ligne)

              --------------------------------------------------------------------------------------------

              En soit, l'exercice d'écrire ce type de matrice est intéressant pour apprendre le RAII. Mais cela ne veut pas dire qu'il faut oublier les principes de la conception objet ;)

              Une règle importante est de penser les classes en termes de services et pas de structure de données. C'est à dire ce qu'elles font et pas ce qu'elles contiennent. Malheureusement, en te focalisant sur l'accès aux données de ta matrice avec [][], tu te focalises principalement sur la structure de données et pas les services.

              Le problème est que si on regarde ce qu'un utilisateur de Matrice attend, ca a être de faire des calculs math avec les matrices, résoudre des équations, etc. L'accès à un membre sera probablement accessoire.

              Or, avec ton implementation, M[k] (une colonne extrait d'une matrice) n'est pas une matrice elle-même. Tu ne pourras pas multiplier directement M[k]*N par exemple, sans ajouter des opérateurs pour supporter le type intermédiaire. Tu devras fournir des surcharges des fonctions pour les différents types (par exemple si tu veux une fonction transpose). Faire des conversions impliquera des copies de données probablement. Etc.

              C'est probablement pour cela qu'on s'embête généralement pas à implémenter [i][j] mais qu'on se contente de (i,j) : parce qu'on préfère se focaliser sur le reste que sur ca. En travaillant sur une matrice qui propose comme unique fonctionnalité [], tu simplifies trop la problématique et fais des choix de conception qui sont trop spécifique à [] et pas à la problématique générale d'implémentation d'une matrice.

              Je te conseille, si tu bosses comme ca sur un classe ou un module, c'est de toujours commencer par définir  correctement tes spécifications, puis tes tests (ie utilise une approche TDD = Test driven dev). Parce que cela a un impact sur comment tu vas implémenter ta classe et donc comment tu vas implémenter le RAII.

              Par exemple, peut être que ton analyse des specs te dira qu'il faut créer des classes Matrice, des vues, etc. Et ne pas retourner un std::vector.

              ----------------------------------------------------------------------------------

              (Désolé des longs messages :) )

              Peut être qu'à ce moment de la lecture, tu te dis "oui, mais osef. Ca ne change rien pour faire mon exo pour apprendre le RAII !". 

              Et c'est là que je sors de mon chapeau magique l'argument "SRP" ! (SRP = single responsibility principle, voir les principes SOLID). Quand tu regardes la problématique générale de l'implémentation d'une matrice (et pas juste l'accès avec [][]), tu vois qu'une classe Matrice a beaucoup de choses à faire. Et par respect du SRP, le mieux est de séparer au mieux les implémentations des fonctionnalités, en particulier l'aspect "matrice" proprement dit, et l'aspect gestion des données.

              Dit autrement, une approche correcte du RAII, c'est pas de créer une classe Matrice qui fait des calculs math ET gère des données, mais de séparer ces responsabilité.

              C'est exactement comme ca que std::vector est implémenté par exemple. Un article a lire : https://www.stroustrup.com/except.pdf. Pour résumer, l'idée est d'implémenter un VectorBase, qui s'occupe que du RAII et une classe Vector, qui utilise en interne VectorBase et propose les fonctionnalités de std::vector.

              Pour ta matrice, un implémentation correcte serait probablement d'implémenter un MatriceBase, qui s'occupe que du RAII, et une classe Matrice. Voire plusieurs classes matrices. Et probablement utiliser des vues.

              ----------------------------------------------------------------------------------

              Et du coup, on peut se poser la question de ce que fait cette classe MatriceBase de plus que les classes de la STL, en particulier std::vector ?

              Et la réponse est probablement rien ! :D

              En fait, c'est pour ca qu'on critique souvent ce genre d'exercice, qui consiste à implémenter des choses existantes. Une façon correcte d'implémenter une matrice est probablement de séparer la partie RAII de l'implémentation de Matrice. Et pour cette partie RAII, le mieux est au final d'utiliser directement les classes de la STL. Implémenter soi même le RAII dans Matrice est très artificiel (et du coup, probablement inutile).

              Je ne dis pas que tu dois arrêter cette exo. Mais cela veut dire que ton exo permet de te focaliser que sur l'aspect syntaxe du RAII, pas de pratiquer correctement le RAII (en particulier savoir quand et comment l'utiliser correctement).

              Et ca sera un constat générale : dans la majorité des cas, on n'implemente pas le RAII, on utilise le RAII de la STL. Et cela va simplifier énormément nos classes, et qu'on va avoir des règles comme la règles du 0 (https://en.cppreference.com/w/cpp/language/rule_of_three), et que l'on ne va pas écrire de destructeur, de copie, etc. dans les classes.

              ----------------------------------------------------------------------------------

              Une question qu'on peut se poser alors, c'est quel exo faire pour apprendre le RAII ?

              A mon sens : implémenter des classes qui représentent des collections minimalistes, qui ne font qu'une chose : contenir des données.

              Par exemple, le plus simple, refaire les classes de la STL : un vector, une liste simple ou doublement chainée, etc. Mais c'est pas forcément intéressant de réécrire des choses existantes, parce que cela veut dire que ca part a la poubelle ensuite.

              Des choses plus intéressantes : écrire des collections qui ne sont pas dans la STL. Regarde par exemple les collections proposées par boost : un circular buffer, des flat collections, etc. Ou regarde ce que les autres langages proposent comme collections.

              -
              Edité par gbdivers 16 novembre 2020 à 19:36:18

              • Partager sur Facebook
              • Partager sur Twitter
                16 novembre 2020 à 20:16:46

                Bonjour ThibaultVlt,

                Tu souhaites ajouter la capacité d'utiliser maMatrice[a][b]. La réponse est on ne peut plus simple : tu l'as déjà! Et oui, essaye avec ton code d'accéder à maMatrice[a][b], ça marche! Ça utilise tout simplement la capacité d'un pointeur à être utilisable comme un tableau.

                Mais attention, comme on te l'a précisé ta fonction operator[]() ne devrait pas retourner un pointeur mais un Vecteur (ou une Matrice{N,1}) car les tableaux bruts ne sont pas de véritables objets et doivent être évités. Pour avoir ton second niveau de [], il suffira que ta classe Vecteur implémente l'operator[]().

                • Partager sur Facebook
                • Partager sur Twitter

                En recherche d'emploi.

                  16 novembre 2020 à 21:49:09

                  > Des choses plus intéressantes : écrire des collections qui ne sont pas dans la STL. Regarde par exemple les collections proposées par boost : un circular buffer, des flat collections, etc. Ou regarde ce que les autres langages proposent comme collections.

                  Et même ça, un circular buffer ou une flat-map, s'implémente très bien comme une surcouche à un vecteur ou même un deque si veut veut permettre un accroissement pas trop couteux. Voire justement, séparer stockage et circular-buffer, c'est plus SRP.

                  In fine, je fais souvent faire l'exo de l'écriture d'une classe RAII avec des matrices linéarisées, cela permet de pratiquer les  fonctions spéciales à la main dans un premier temps. Ce n'est au fond qu'un kata de plus qu'il est intéressant de savoir faire les yeux fermés -- arrivé à un certain moment. Et de bien expliquer qu'en vrai, ce n'est pas comme ça comme implémente des classes supports pour de l'algèbre linaire et qu'il vaut mieux utiliser une des quelques libs spécilisée qui existe déjà.

                  • Partager sur Facebook
                  • Partager sur Twitter
                  C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                    17 novembre 2020 à 13:57:18

                    Il y a une phrase de ton article gb qui m'avait interpellée. J'en comprenais l'idée générale mais j'avais du mal a visualiser concrètement son application 

                    "Pour éviter cette problématique, le mieux est d'avoir une classe qui ne fait qu'une seule chose : gérer la ressource (et donc ne pas pouvoir lancer d'autres exceptions dans le constructeur que l'acquisition de la ressource. De plus, pour des raisons de respect du Principe de Responsabilité Unique (SRP), une classe RAII ne doit rien faire d'autre que de gérer une ressource)."

                    Si je comprend bien c'est ce que tu m'as expliqué dans ton précédent message ? Séparer les calculs matriciels d'un côté dans une classe Matrice et une classe MatriceBase qui s'occupe uniquement d'allouer l'espace mémoire nécéssaire au stockage de la matrice et de le libérer le moment venue.

                    Mais si j'ai bien compris, une exception appelle le destructeur de l'objet. Si j'ai une exception dans une fonction qui multiplie ma matrice avec une autre et que le programme crash, le destructeur de chacune de mes matrices sera appellé et libérera la mémoire non ? Séparer l'allocation dans une classe MatriceBase c'est pour faciliter la maintenabilité du code du coup ?


                    Si j'essaye de modifier mon code en fonction de vos conseils, j'imagine que ça donnerait quelque chose comme ça n'est ce pas ?

                    Matrice.h

                    class MatriceBase {
                    protected:
                    	int* matrix;
                    
                    	MatriceBase(const std::size_t rows, const std::size_t cols)
                    	{
                    		matrix = new int[rows * cols]; // Tableau linéarisé 
                    	}
                    	~MatriceBase()
                    	{
                    		delete[] matrix;
                    	}
                    };
                    
                    
                    public:
                    	Matrice(const std::size_t rows, const std::size_t cols);
                    	~Matrice();
                    
                            // ---- Operator overloading ----
                    
                    	int& operator()(std::size_t line, std::size_t col)
                    	{
                    		return matrix[line * (_cols - 1) + col];
                    	}
                    
                    private:
                    	std::size_t _rows;
                    	std::size_t _cols;
                    
                    };


                    le constructeur de Matrice :

                    Matrice::Matrice(const std::size_t rows, const std::size_t cols) : MatriceBase(rows,cols)
                    {
                    	_rows = rows;
                    	_cols = cols;	
                    }

                    A priori je n'ai rien à mettre dans le destructeur de Matrice, le destructeur de matrice base sera appelé à la fin de vie de chaque objet Matrice ?



                    -
                    Edité par ThibaultVnt 17 novembre 2020 à 14:14:35

                    • Partager sur Facebook
                    • Partager sur Twitter
                      17 novembre 2020 à 14:56:05

                      Meme si techniquement c'est possible, auquel cas j'emploierai l'héritage privé, je préfère de loin un MatrixStorage qui est un membre. Je n'aime pas abuser de l'héritage pour factoriser du code.

                      PS: https://cpp.developpez.com/faq/cpp/?page=Norme#Quels-sont-les-identificateurs-interdits-par-la-norme

                      • Partager sur Facebook
                      • Partager sur Twitter
                      C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                        17 novembre 2020 à 16:18:51

                        Je ne suis pas sur de comprendre ce que tu veux dire Imghs, toi tu ferais quelque chose qui ressemble à ça ?

                        class MatriceBase {
                        public:
                        	int* matrix;
                        
                        	MatriceBase(const std::size_t rows, const std::size_t cols)
                        	{
                        		matrix = new int[rows * cols]; // Tableau linéarisé 
                        	}
                        	~MatriceBase()
                        	{
                        		delete[] matrix;
                        	}
                        };
                        
                        class Matrice {
                        public:
                        	Matrice(const std::size_t rows, const std::size_t cols)
                        	{
                        		base = new MatriceBase(rows, cols);	// Seul ligne du constructeur pouvant lever une exception
                        		rows_ = rows;
                        		cols_ = cols;
                        	}
                        	~Matrice()
                        	{
                        		delete base;
                        	}
                        
                        	int& operator()(std::size_t line, std::size_t col)
                        	{
                        		return base->matrix[line * (cols_ - 1) + col];
                        	}
                        
                        private:
                        	MatriceBase* base;
                        
                        	std::size_t rows_;
                        	std::size_t cols_;
                        
                        };

                        J'ai hésité entre les deux approches, du coup j'ai suivi ce qu'a fait Stroustrup dans le lien que m'a link gbdivers

                        Mais c'est vrai que lui a fait un héritage privée. Pour le coup je comprend pas la différence. Dans ma classe Matrice j'ai besoin d'avoir accès au tableau d'entier initialisé dans MatriceBase et au constructeur de MatriceBase.

                        Donc qu'est ce qui change entre avoir int* matrix en public et faire de l'héritage privée ou le mettre en protected et faire de l'héritage public ? 

                        Avec le constructeur de MatriceBase en public je peux même créer un objet MatriceBase dans mon main. Ca me parait .... pas souhaitable du tout, ça a rien à faire dans un main.

                        Pourrais tu m'expliquer un peu plus précisemment ce que tu as en tête stp ?

                        • Partager sur Facebook
                        • Partager sur Twitter
                          17 novembre 2020 à 19:22:20

                          Presque, je serai plus proche de ça, où le stockage n'est pas un pointeur. On cherche à les éliminer, pourquoi en remettre?

                          #include <cstddef>
                          #include <algorithm>
                          #include <cassert>
                          
                          
                          
                          /**
                           * Default Storage class for matrices.
                           * @tparam T  Element type
                           *
                           * @invariant Actual m_buffer size == nb_elems()
                           */
                          template <typename T>
                          struct MatrixStorage
                          {
                              using value_type       = T;
                              using reference        = T&;
                              using const_reference  = T const&;
                              using pointer          = T*;
                          
                              MatrixStorage(std::size_t n)
                                  : m_n(n)
                                  , m_buffer(new value_type[n])
                                  {}
                              MatrixStorage(std::size_t l, std::size_t c, value_type def)
                                  : MatrixStorage(l, c)
                                  {
                                      std::fill_n(m_buffer, m_buffer + nb_elems(), def);
                                  }
                              ~MatrixStorage() { delete[] m_buffer; }
                          
                              MatrixStorage(MatrixStorage const& rhs)
                                  : MatrixStorage(rhs.lines(), rhs.cols())
                                  {
                                      std::copy_n(rhs.m_buffer, rhs.m_buffer+nb_elems(), m_buffer);
                                  }
                              MatrixStorage & operator=(MatrixStorage const& rhs) {
                                  if (rhs.nb_elems() == nb_elems()) {
                                      std::copy_n(rhs.m_buffer, rhs.m_buffer+nb_elems(), m_buffer);
                                  }
                                  else {
                                      MatrixStorage tmp = rhs;
                                      swap(tmp);
                                  }
                                  return *this;
                              }
                          
                              MatrixStorage(MatrixStorage && rhs) noexcept {
                          #if 1
                                  swap(rhs); // vive member default init
                          #else
                                  // ou, plus optimal
                                  m_n      = std::exchange(rhs.m_n, 0);
                                  m_buffer = std::exchange(rhs.m_n, nullptr);
                          #endif
                              }
                              MatrixStorage& operator=(MatrixStorage && rhs) noexcept {
                          #if 1
                                  // Version échange de patate chaude
                                  swap(rhs);
                          #else
                                  // Version RAZ
                                  m_n      = std::exchange(rhs.m_n, 0);
                                  delete[] m_buffer;
                                  m_buffer = std::exchange(rhs.m_n, nullptr);
                          #endif
                                  return *this;
                              }
                          
                              void swap(MatrixStorage & other) noexcept {
                                  using std::swap;
                                  swap(m_n, other.m_n);
                                  swap(m_buffer, other.m_buffer);
                              }
                          
                              std::size_t nb_elems() const noexcept
                              { return m_n; }
                          
                              reference operator[](std::size_t i) noexcept {
                                  assert(i < nb_elems());
                                  return m_buffer[i];
                              }
                              const_reference operator[](std::size_t i) const noexcept {
                                  assert(i < nb_elems());
                                  return m_buffer[i];
                              }
                          private:
                              std::size_t m_n      = 0;
                              pointer     m_buffer = nullptr;
                          };
                          
                          /**
                           * Default Storage class for matrices.
                           * @tparam T  Element type
                           *
                           * @invariant Actual storage size == lines() * columns();
                           */
                          template <typename T, template<typename> class Storage>
                          struct Matrix
                          {
                              using storage_type     = Storage<T>;
                              using value_type       = typename storage_type::value_type;
                              using reference        = typename storage_type::reference;
                              using const_reference  = typename storage_type::const_reference;
                          
                              Matrix(std::size_t l, std::size_t c)
                                  : m_l(l)
                                  , m_c(c)
                                  , m_storage(l*c)
                                  {}
                              Matrix(std::size_t l, std::size_t c, value_type def)
                                  : m_l(l)
                                  , m_c(c)
                                  , m_storage(l*c, def)
                                  {}
                              ~Matrix() = default;
                          
                              Matrix(Matrix const& rhs) = default;
                              Matrix & operator=(Matrix const& rhs) = default;
                          
                              Matrix(Matrix && rhs) noexcept {
                                  // A cause de l'invariant ligs*cols == buffer size, =default n'est
                                  // pas possible.
                          
                                  swap(rhs); // vive member default init
                              }
                              Matrix& operator=(Matrix && rhs) noexcept {
                          #if 1
                                  // version patate chaude
                                  swap(rhs);
                          #else
                                  // ou version plus efficace
                                  m_l = std::exchange(rhs.m_l, 0);
                                  m_c = std::exchange(rhs.m_c, 0);
                                  m_storage = std::move(rhs.m_storage);
                          #endif
                                  return *this;
                              }
                          
                              void swap(Matrix & other) noexcept {
                                  using std::swap;
                                  swap(m_l, other.m_l);
                                  swap(m_c, other.m_c);
                                  m_storage.swap(other.m_storage); // j'ai la flemme de définir swap dans l'espace de noms de MatrixStorage
                              }
                          
                              std::size_t lines  () const noexcept { return m_l; }
                              std::size_t columns() const noexcept { return m_c; }
                          
                              reference       operator()(std::size_t l, std::size_t c)       noexcept
                              { return m_storage[offset(l, c)]; }
                          
                              const_reference operator()(std::size_t l, std::size_t c) const noexcept
                              { return m_storage[offset(l, c)]; }
                          
                              // + les opérateurs
                          private:
                              std::size_t offset(std::size_t l, std::size_t c) const noexcept {
                                  assert(l < lines());
                                  assert(c < columns());
                                  return columns() * l + c;
                              }
                          
                              std::size_t nb_elems() const noexcept
                              { return m_storage.nb_elems(); }
                          
                              std::size_t m_l = 0;
                              std::size_t m_c = 0;
                              storage_type m_storage;
                          };
                          



                          Mais il y a encore plein de choses pas terribles à améliorer: nb_elements+ligs&cols, c'est trop. Et je n'ai pas exactement réfléchi à comment étendre plus facilement les fonctions d'allocations (pour en avoir des alignées p.ex.).

                          L'aternative, c'est pour moi de l'héritage privé d'un paramètre template et des usings.

                          -
                          Edité par lmghs 17 novembre 2020 à 19:26:43

                          • Partager sur Facebook
                          • Partager sur Twitter
                          C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                            17 novembre 2020 à 21:26:21

                            Ok, très complet merci. Je vais étudier ça en prendre le temps de digérer tous vos conseils

                            Je vais passer le sujet en résolu je pense que la question de base a largement été répondue et même plus.

                            Merci à tous :)

                            • Partager sur Facebook
                            • Partager sur Twitter
                              17 novembre 2020 à 21:45:50

                              Hum, et utiliser des clases imbriqués si on veut fermer l'objet ? MatrixStorage a t'elle un sens d'utilisation à l'extérieur de Matrix ?
                              • Partager sur Facebook
                              • Partager sur Twitter

                              Ma chaine youtube, une notion de c++ en 5 min !

                                17 novembre 2020 à 22:34:26

                                @SixtyOne, Qu'entends-tu par "fermer l'objet"?

                                Dans l'état c'est plus un Storage tout court. Je vois que la propal de mdarray va dans un sens similaire: https://isocpp.org/files/papers/D1684R0.html

                                • Partager sur Facebook
                                • Partager sur Twitter
                                C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                  18 novembre 2020 à 9:19:11

                                  Dans le sens où Matrix storage doit elle être utilisé en dehors de Matrix ?

                                  En gros, MatrixStorage a t'il un sens uniquement dans Matrix ? MatrixStorage est il dévoué à servir d'outil de gestion pour la classe Matrix ? Et enfin, est ce que MatrixStorage a un sens sans Matrix ?

                                  • Partager sur Facebook
                                  • Partager sur Twitter

                                  Ma chaine youtube, une notion de c++ en 5 min !

                                    18 novembre 2020 à 11:33:48

                                    SixtyOne a écrit:

                                    Dans le sens où Matrix storage doit elle être utilisé en dehors de Matrix ?

                                    En gros, MatrixStorage a t'il un sens uniquement dans Matrix ? MatrixStorage est il dévoué à servir d'outil de gestion pour la classe Matrix ? Et enfin, est ce que MatrixStorage a un sens sans Matrix ?


                                    En l'état, ce n'est jamais qu'un vecteur non redimensionnable mais toujours copiable et déplaçable -- et affranchi en avance sur le standard de la règle de Lakos. Elle peut donc être utilisée en plein d'autres endroits.

                                    Maintenant elle pourrait ne pas l'etre, l'objectif derrière était d'en faire une politique (au sens de /Modern C++ Design/ D'Andrei Alexandrescu): un paramètre template (son interface définissant un concept meme) pour les classes matrices afin de controler comment le stockage est réalisé. Ici, on peut intervenir pour avoir un stockage linéaire (aligné ou pas), à multiple allocs, pour matrices creuses (ce qui me conforte dans le fait que le désign est complètement à revoir), etc

                                    Bref si la classe, que dis-je Concept!, de Storage1D n'est employé qu'ici pour l'instant, il reste à vocation de n’être qu'un point de variation, une politique pour gérer les stockages.

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                                      18 novembre 2020 à 13:45:32

                                      Parfait ! (c'était aussi pour montrer les questions que l'on peu se poser quand nous devons architecturer un programme)
                                      • Partager sur Facebook
                                      • Partager sur Twitter

                                      Ma chaine youtube, une notion de c++ en 5 min !

                                      Surcharge opérateur [][]

                                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                      • Editeur
                                      • Markdown