Partage
  • Partager sur Facebook
  • Partager sur Twitter

Problème manipulation de tableaux

    18 juin 2021 à 16:43:53

    Bonjour à tous,

    Je me suis lancé récemment dans un petit projet pour coder plusieurs petites opérations mathématiques.

    J'en suis actuellement à coder le produit matriciel entre un vecteur colonne et une matrice. Cependant, j'ai l'erreur de compilation suivante lorsque j'essaye d'utiliser ma classe:

    error: initializer for flexible array member ‘const int Vector<int>::m_values []’
       54 | Vector<T>::Vector(const unsigned int size, const int *values):m_size(size), m_values(values) {}
          |

    Voici le code de ma classe Vector (Tout n'est pas parfait donc n'hésitez pas à me donner des suggestions aussi ;) ):

    //
    // Created by arthur on 18/06/2021.
    //
    
    #ifndef VECTOR_HPP
    #define VECTOR_HPP
    
    #include <iostream>
    #include <cmath>
    
    template <typename T>
    class Vector {
    
    public:
        Vector(unsigned int size, const T *values);
    
        static Vector<T> sum(Vector<T> vector1, Vector<T> vector2 );
    
        unsigned int getSize() const;
        T* getVectorValues() const;
        void printVector() const;
        T getEuclidianNorm() const;
    
    private:
        const unsigned int m_size;
        const T m_values[];
    };
    
    template<typename T>
    unsigned int Vector<T>::getSize() const
    {
        return m_size;
    }
    
    template<typename T>
    T *Vector<T>::getVectorValues() const
    {
        return m_values;
    }
    
    template<typename T>
    void Vector<T>::printVector() const
    {
        std::cout << "┌ \t ┐" << std::endl ;
        for(unsigned int i = 0; i<m_size; i++)
        {
            std::cout << "| " << m_values[i] << " |" << std::endl ;
        }
        std::cout << "└ \t ┘" << std::endl ;
    
    }
    
    template<typename T>
    Vector<T>::Vector(const unsigned int size, const T *values):m_size(size), m_values(values) {}
    
    template<typename T>
    T Vector<T>::getEuclidianNorm() const
    {
        T returnedNorm = 0;
        for(int i = 0; i<m_size; i++)
            returnedNorm += m_values[i]*m_values[i];
        returnedNorm = std::pow(returnedNorm,0.5);
        return returnedNorm;
    }
    
    template<typename T>
    Vector<T> Vector<T>::sum(Vector<T> vector1, Vector<T> vector2) {
        if(vector1.m_size == vector2.m_size)
        {
            T* values = new T[vector1.m_size];
            for (int i = 0; i < vector1.m_size; ++i)
            {
                values[i] = vector1.m_values[i] + vector2.m_values[i];
            }
            return Vector<T>(vector1.m_size, values);
        }
        else
        {
            throw std::length_error("Trying to sum two vectors with different sizes!");
        }
    }
    
    
    #endif //VECTOR_HPP
    

    Ensuite, j'essaye de l'utiliser comme cela:

    int main(int argc, char** argv)
    {
        int vect_values[3] = {1,2,3};
        auto first_vect = new Vector<int>(3, vect_values);
    }



    Pour ceux qui se demanderaient pourquoi j'utilise des patrons (templates en anglais :diable:), l'idée est de pouvoir spécifier le type des éléments de la matrice/vecteur.

    Je vous remercie par avance pour votre aide,

    -
    Edité par ArthurDaVinci 18 juin 2021 à 17:25:43

    • Partager sur Facebook
    • Partager sur Twitter
      18 juin 2021 à 17:11:38

      Bonjour, 

      Je pense que m_values devrait être un std::vector<T>

      Ça donnerait un code bien plus sûr, et règlerait probablement le problème...

      • Partager sur Facebook
      • Partager sur Twitter
        18 juin 2021 à 17:33:44

        J'ai édité mon message car j'avais laissé une erreur bête dans le code.


        Umbre37 a écrit:

        Bonjour, 

        Je pense que m_values devrait être un std::vector<T>

        Ça donnerait un code bien plus sûr, et règlerait probablement le problème...



        Merci pour ta suggestion! Je ferai ça si personne n'a d'idées pour résoudre le soucis :) J'essaye de ne pas utiliser vector car la taille de mon vecteur est fixe, d'un point de vue purement théorique, je n'ai donc pas besoin d'utiliser un tableau dynamique. Aussi, je ne maîtrise pas vraiment l'espace mémoire utilisée par la classe vector (même si je peux lui faire faire un shrink_to_fit il me semble).
        • Partager sur Facebook
        • Partager sur Twitter
          18 juin 2021 à 17:58:50

          Bonjour,

          C'est vrai que faire que ton Vector<T> contienne un std::vector<T> peut sembler curieux. Pourtant bien que les noms se ressemblent, chacun va jouer son rôle. Le std::vector est un objet capable de stocker proprement et sans risque un tableau de données, ton Vector lui gère tout ce que l'on peut faire sur l'objet mathématique vecteur.

          Tu sembles souhaiter gérer toi même tes allocations, c'est quasi-impossible et tu es très mal parti. Je ne sais pas où tu as appris l'opérateur new, mais il est préférable de l'oublier. Je pratique et enseigne le C++ depuis 30 ans, et je m'en passe très bien.

          Je suis étonné que la ligne 26 de ton code compile. C'est une syntaxe directement issue du C qui ne fait du tout ce que tu penses. Et même si m_values était un tableau brut à taille non définie la ligne ne compilerait pas non plus.

          Donc je te propose aussi de stocker des données dans un std:vector<T>.

          • Partager sur Facebook
          • Partager sur Twitter

          En recherche d'emploi.

            18 juin 2021 à 18:26:09

            Tu as plusieurs gros problèmes.

            a- Suivant les phases de lune ton vecteur possède ou ne possède pas la mémoire qu'il contient. Rien de tel pour avoir des soucis et un code in-maintenable.

            Bref, là tu as des fuites à gogo. Et une correction naive des fuites aura pour effet de provoquer des double-libérations. std::vecteur c'est bien. Et si tu veux une somme entre une vue et un vecteur possesseur, il faudrait définir un type comme span (C++20)/mdspan(C++23) et écrire l'addition sur eux.

            b-  `new Vector` Laisse moi deviner, tu viens du Java (ou C#)? :) En C++ moins on utilise new, mieux on se porte. A vrai dire une règle qualité pour écrire du code métier robuste est qu'un code C++ ne devrait contenir ni new, ni delete. Et oui, c'est parfaitement possible. (cf le tuto de Zeste de Savoir).

            c- `sum` devrait s'appeler operator+(), fonction libre et amie (situation idéale), et ici, cela aurait parfaitement du sens de la faire dépende de += (membre)

            d- `getVectorValues` devrait renvoyer un `T const*`, ou mieux, ne pas exister et s'appeler operator[].

            e-  `std::pow(returnedNorm,0.5);`: `std::sqrt` c'est bien et clair, et mieux: pas de constante magique, et surtout pas de mélange possible entre le type de T et le type de 0.5 qui aura pour effet de pourrir encore plus les perfs de la fonction.

            f- l'addition de vecteurs de tailles différentes est pour moi un erreur de programmation et un assert() est parfait pour régler le problème.

            g- `printVector()`, le truc canonique tout langage confondu: ne jamais écrire directement dans la console depuis une classe métier. En C++, on définira l'opérateur<<.

            Bref, je ne peux que te conseiller de profiter de cet exercice pour te familiariser avec les idiomes du C++ et son utilisation canonique, en oubliant les idiomes des langages d'où tu viens. L'exo sur la matrice du tuto de Zeste de Savoir est un excellent point de départ, ainsi que le chapitre juste avant sur la sémantique de valeur. Par contre, il n'aborde pas un seul instant la gestion manuelle de la mémoire car... on ne le veut pas en général, et que c'est un sujet relativement avancé: pour le faire bien (robuste et correct) en fait on ne le fait pas, et on utiliserai un std::unique_ptr<T[]>`

            PS: >templates en anglais

            Ca ne vient jamais que du vieux françois templet (https://en.wiktionary.org/wiki/template#Etymology)... alors bon.

            -
            Edité par lmghs 18 juin 2021 à 18:35:10

            • 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.
              19 juin 2021 à 11:51:20

              Bonjour, merci à tous pour vos messages et vos explications!  Je viens du C et de Java en effet (ça se voit tant que ça? :D), d'où les approximations de syntaxe et les mauvaises habitudes.

              Dalfab a écrit:

              Tu sembles souhaiter gérer toi même tes allocations, c'est quasi-impossible et tu es très mal parti. Je ne sais pas où tu as appris l'opérateur new, mais il est préférable de l'oublier. Je pratique et enseigne le C++ depuis 30 ans, et je m'en passe très bien.

              Je croyais qu'on instanciait les objets de cette façon (comme en Java). Je vais donc me passer de cette mauvaise habitude :)

              lmghs a écrit:

              Tu as plusieurs gros problèmes.

              a- Suivant les phases de lune ton vecteur possède ou ne possède pas la mémoire qu'il contient. Rien de tel pour avoir des soucis et un code in-maintenable.

              Bref, là tu as des fuites à gogo. Et une correction naive des fuites aura pour effet de provoquer des double-libérations. std::vecteur c'est bien. Et si tu veux une somme entre une vue et un vecteur possesseur, il faudrait définir un type comme span (C++20)/mdspan(C++23) et écrire l'addition sur eux.

              Je ne suis pas sûr de bien comprendre ta première phrase, pourrais-tu m'expliquer pourquoi c'est le cas?

              Je vais aller m'informer sur span alors.

              c- `sum` devrait s'appeler operator+(), fonction libre et amie (situation idéale), et ici, cela aurait parfaitement du sens de la faire dépende de += (membre)

              d- `getVectorValues` devrait renvoyer un `T const*`, ou mieux, ne pas exister et s'appeler operator[].

              g- `printVector()`, le truc canonique tout langage confondu: ne jamais écrire directement dans la console depuis une classe métier. En C++, on définira l'opérateur<<.

              Je suis pas encore super habitué avec la surcharge d'opérateur en effet. Je vais faire ça comme ça !

              Je ne connaissais pas le tuto de Zest de Savoir, je pense que j'ai donc un peu de lecture à faire :p

              Sinon pour la mémoire, je ne comprends pas pourquoi en C++ on ne s'en occupe presque pas et surtout comment ça se fait que ce soit possible? Je croyais qu'il n'y avait pas de ramasse-miettes.

              En fait, je m'intéresse en ce moment à l'HPC (High Performance Computing) et j'avais commencé ce projet pour implémenter divers algorithmes pour gérer les matrices creuses. C'est pour ça que je voulais gérer la mémoire moi-même pour pouvoir minimiser la place prise dans le cache. Je m'étais donc mis au C++ pour profiter de l'héritage, polymorphisme, etc... qu'on n'a pas en C mais apparemment vous avez l'air de dire que c'est plus compliqué que ce que je pensais. Auriez-vous tout de même de bonnes ressources pour apprendre à gérer ça? (Ou peut-être que vous avez d'autres suggestions de langages plu adaptés).

              Merci beaucoup pour votre aide





              -
              Edité par ArthurDaVinci 19 juin 2021 à 11:55:20

              • Partager sur Facebook
              • Partager sur Twitter
                19 juin 2021 à 13:15:39

                a- > Je ne suis pas sûr de bien comprendre ta première phrase, pourrais-tu m'expliquer pourquoi c'est le cas?

                Ton constructeur agit comme une vue, c'est à dire qu'il n'est pas responsable de la mémoire. Elle est allouée et gérée ailleurs.

                Sauf dans le cas de la somme. Là, ce que tu retournes est seul responsable de la mémoire. Et quand un objet est ou n'est pas responsable, c'est l'enfer. Il sauf faire simple: soit toujours oui (std::vector), soit toujours non (std::span (c++20).

                b- > Sinon pour la mémoire, je ne comprends pas pourquoi en C++ on ne s'en occupe presque pas et surtout comment ça se fait que ce soit possible? Je croyais qu'il n'y avait pas de ramasse-miettes.

                C'est parce que tu n'as pas encore croisé de vrai cours de C++. La vraie grosse chose qui démarque le C++ du C, c'est qu'en C++ on a un outil unique qui permet de garantir la restitution des ressources (mémoire, fichier, socket, mutex...) quelque soit le chemin d'exécution pris (exception comprises donc, retour anticipé compris aussi; plus besoin d'un if toutes les 2 lignes).

                Le mot clé pour en savoir plus, c'est RAII.

                > En fait, je m'intéresse en ce moment à l'HPC (High Performance Computing) et j'avais commencé ce projet pour implémenter divers algorithmes pour gérer les matrices creuses.

                Alors. Si tu veux vraiment faire du HPC, utilise une lib dédiée. Ce domaine est maitrisé depuis plus de 20ans et il a été multiplement peaufiné depuis. L'écriture de classe matrice est un très bon exo du niveau débutant au niveau  expert++. Si tu veux apprendre le C++, parfait! Si tu veux faire du calcul HP, utilise une lib dédiée! -> eigen, blaze, amadillo parmi les plus avancées et de qualité industrielle aujourd'hui.

                > Je m'étais donc mis au C++ pour profiter de l'héritage, polymorphisme, etc... qu'on n'a pas en C mais apparemment

                La grande force du C++ par rapport au C, c'est le RAII.

                Après, il y a des petites trucs sympas comme un meilleur typage, la possibilité de définir des classes, l'OO... Mais la vraie révolution, c'est les destructeurs, et donc le RAII.

                > Ou peut-être que vous avez d'autres suggestions de langages plu adaptés

                Aujourd'hui, pour beaucoup d'utilisateurs, numpy de python est une bonne première approximation. Je suis quasi sûr que ce sera plus rapide que n'importe qu'elle implémentation naïve que l'on pourrait écrire à la main, même en réussissant à mettre en oeuvre des expression template pour éliminer les temporaires: numpy (comme eigen et cie) bénéficient déjà de libs bas niveau extrêmement optimisées pour vectoriser, paralléliser, etc. Mais il est vrai, que le C++ permettra de faire encore mieux sur certains algos où l'on doit sortir numba pour aller encore plus loin en python (mais toujours pas assez)

                Julia est un langage qui semble pas mal en vogue pour le HPC, pas testé encore.

                Bref, pour apprendre le C++, un bon départ en ligne: le tuto de ZdS.

                -
                Edité par lmghs 19 juin 2021 à 17:41:57

                • 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.
                  22 juin 2021 à 11:33:27

                  Merci pour ton retour! J'ai passé pas mal de temps à lire le petit cours de ZdS et j'ai commencé à lire les guidelines de C++17 pour mieux comprendre comment coder en C++ moderne. Malgré tes suggestions de bibliothèques, je continue mon projet car les bibliothèques que j'ai trouvées n’implémentent pas tout les formats de matrices dont j'ai besoin. J'ai un peu avancé mais j'ai quand même deux questions que je me permets de poser ici pour ne pas faire 10 sujets... (si c'est mieux de faire un autre sujet je le ferai).

                  1) Comme je veux faire plusieurs types de matrices, j'ai voulu faire l'équivalent d'une interface en Java pour définir ce que je voulais que mes différentes implémentations de matrices puissent faire. J'ai vu sur internet qu'un équivalent de ça était de faire une classe avec des fonctions membres virtuelles. Je voulais savoir si cet équivalent était un bon idiome de c++ ou si je devrais faire ce que je veux faire d'une autre façon. Ma classe ressemble à ça :

                  /**
                   * @brief A virtual class for matrix
                   * @details This class describes how the various matrix implementations should work and what they should be able to do
                   * @tparam numberType a type for the numbers in the vector. For example : int, float, double, etc
                   * @note The used type should support basic arithmetic operations
                   */
                  template <typename numberType>
                  class Matrix
                  {
                  public:
                  
                      /**
                       * @brief Default destructor for a matrix
                       */
                      virtual ~Matrix() = default;
                  
                      /**
                       * @brief Default assignation by copy operator
                       * @param matrix matrix to be copied
                       * @return A copy of the given matrix
                       */
                      Matrix<numberType>& operator=(Matrix<numberType> const& matrix) = default;
                  
                      /**
                       * @brief Matrix access operator
                       * @param i index of the line to be accessed
                       * @pre i should be smaller than the number of lines of the matrix
                       * @param j index of the column to be accessed
                       * @pre j should be smaller than the number of lines of the matrix
                       * @return The value at the line i and the column j
                       */
                      virtual numberType const & operator()(std::size_t i, std::size_t j) const = 0;
                  
                      /**
                       * @brief Matrix access operator
                       * @param i index of the line to be accessed
                       * @pre i should be smaller than the number of lines of the matrix
                       * @param j index of the column to be accessed
                       * @pre j should be smaller than the number of lines of the matrix
                       * @return The value at the line i and the column j
                       */
                      virtual numberType& operator()(std::size_t i, std::size_t j) = 0;
                  
                      /**
                       * @brief Multiplies a matrix by a vector
                       * @param vector that multiplies the matrix
                       * @return result of the multiplication
                       * @pre the size of the vector should be equal to the number of columns of the matrix
                       */
                      virtual Vector<numberType> operator*(Vector<numberType> vector) = 0;
                  
                  
                      /**
                       * @brief Gives the number of lines of the matrix
                       * @return the number of lines of the matrix
                       */
                      virtual std::size_t getLinesCount() const noexcept = 0;
                  
                      /**
                       * @brief Gives the number of columns of the matrix
                       * @return the number of columns of the matrix
                       */
                      virtual std::size_t getColumnsCount() const noexcept = 0;
                  
                  };
                  

                  2) Alors là j'espère que ça va pas vous énerver (:p) mais je voudrais pouvoir faire un constructeur qui accepte un tableau à 2 dimensions et pouvoir construire une matrice à partir de ça. Pour un vecteur, c'est plus simple car on donne juste un tableau et  j'ai réussi à le faire comme ça:

                  Vector(std::initializer_list<numberType> values);

                  Ce qui me permet d'instancier des vecteurs de la façon suivante:

                  Vector<int> vector = { 2,3 };

                  Je voudrais donc pouvoir disposer d'un constructeur pour mes matrices qui puisse me donner la syntaxe suivante :

                  RegularMatrix<int> matrix = { {1,2} , {2,3} }

                  Merci!



                  • Partager sur Facebook
                  • Partager sur Twitter
                    22 juin 2021 à 12:56:22

                    1- Le problème est que la sémantique de valeur attendue des matrices n'est pas vraiment compatible avec les interfaces. Maintenant parfois j'ai vu des exceptions avec les matrices pour vraiment avoir un polymorphisme dynamique. Perso, le polymorphisme statique me suffit et je n'ai jamais eu besoin de hiérarchie de fait. Vive le duck typing!

                    Tu pourrais nous dire ce dont tu avais besoin que tu n'as pas retrouvé dans les libs industrielles?

                    2- il faut une initializer_list d'initializer_list, et après vérifier que les dimensions matchent.

                    • 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.
                      22 juin 2021 à 15:35:02

                      Merci de ta réponse!

                      En fait je m'intéresse au problème SpMV (Sparse Matrix-Vector multiplication/ Multiplication Matrice Creuse- Vecteur en suisse) et la plupart des bibliothèques s'intéressent soit à des formats de matrice creuses très classiques ( COO, CSR, ELL) soit n'implémentent que leur propre format qu'ils ont développé (souvent des résultats de recherche). Il se trouve que tout ces formats ont des avantages et des inconvénients et sont plus ou moins performants suivant les matrices et suivant qu'on exécute le programme sur processeur ou sur GPU. Ce dont j'ai besoin c'est d'une bibliothèque qui implémente l'intégralité des formats usuels et divers formats moins usuels proposés par des chercheurs pour ensuite comparer les temps d'exécution de chaque format sur plusieurs matrices et sur processeur/gpu.

                      2) Désolé, j'ai oublié de préciser que j'avais déjà essayé de faire ça et que j'obtiens l'erreur :

                      error: could not convert ‘{{1, 2}, {3, 4}}’ from ‘<brace-enclosed initializer list>’ to ‘RegularMatrix<int>’

                      avec le constructeur suivant :

                      RegularMatrix( std::size_t nb_rows, std::size_t nb_columns, std::initializer_list<std::initializer_list<numberType>> arrayInitValue);

                      Je pense que le problème peut venir du fait que initializer_list ne peut pas prendre de class template, mais je ne suis pas sur


                      • Partager sur Facebook
                      • Partager sur Twitter
                        22 juin 2021 à 16:12:08

                        Toutes les perfs que tu penses gagner, tu vas les perdre deux fois sur cette signature: Vector<numberType> operator*(Vector<numberType> vector)

                        Les passages par valeur vont impliquer des allocations dynamiques à un moment ou un autre. Et ça, c'est parmi ce qu'il y a de pire. En supposant que tu n'aies pas rajouté une hiérarchie en plus sur les vecteurs.

                        Tu devrais regarder entièrement comment ils font avant de partir bille en tête. En général les libs C++ sont en deux couches: une couche basse type BLAS (plusieurs libs vont même être capables d'exploiter les libs de références si détectées: Intel.MKL, LAPACK..., et d'avoir leur propre alternative si rien de tel n'est trouvé lors de l'install/la compil). Ensuite il y a la couche haute qui transforme des appels haut niveaux qui manipulent des objets de type matrice ou vecteur en appel à des routines BLAS, ou le cas échéant à des déroulages de boucles afin d'éviter au maximum toute allocation) Et il y a certainement encore d'autres techniques à l'oeuvre depuis que j'ai regardé.

                        C'est pour ça que quand je vois une volonté de benchmarker des calculs et des structures de données précises et à côté des objets naifs...

                        2- Pour les constructeurs, la double initializer_list suffit. Nul besoin de tailles en plus. Et si tu veux supporter les conversions de types, il faudra rendre le constructeur template sur le type des éléments dans la liste.

                        • 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.

                        Problème manipulation de tableaux

                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                        • Editeur
                        • Markdown