Partage
  • Partager sur Facebook
  • Partager sur Twitter

constructeur de recopie pointeur

    10 octobre 2020 à 12:22:36

    Bonjour à tous, je travaille sur une classe possédant entre autre un tableau comme attribut  et je souhaiterais que le constructeur de recopie fasse pointer le tableau sur le même tableau que celui de l'objet recopié.

    des idées ?

    merci d'avance pour votre aide

    • Partager sur Facebook
    • Partager sur Twitter
      10 octobre 2020 à 12:39:22

      Bonjour, ce doit être faisable avec un constructeur qui prend une référence vers l'objet. Néanmoins je trouve ta demande étrange (si je l'ai bien comprise) : tu veux pointer vers un tableau donc tu vas avoir en attribut de ta classe un pointeur, et tu veux utiliser le constructeur de copie pour pointer vers donc ce même tableau en attribut ? Tu ferais un pointeur de pointeur ici donc ?

      De plus il ne s'agit alors plus d'un constructeur de copie, qui comme son nom l'indique ne doit servir qu'à COPIER un objet et ne doit en aucun cas modifier l'objet passé en copie, c'est pour cette raison qu'on déclare l'objet en copie comme constant d'ailleurs.

      Peut-être peux-tu donner plus de détails ?

      • Partager sur Facebook
      • Partager sur Twitter
        10 octobre 2020 à 15:04:12

        L'idée, si je la comprends me parait saugrenue. Le but est que toutes les copies partagent en fait le meme tableau façon référence comme dans les autres langages.

        Il y a un certain nombre de problèmes derrière ces copies superficielles et partages: on a des pseudo-globales, on ne sait plus trop qui possede quoi, on introduit des complications en milieu multithréadé.

        Version simple, je ferai du membre tableau un std::shared_ptr<vector<T>>. On perd en perfs, mais on a plein de services. Il y a aussi le std::shared_ptr<T[]> (oui, il y a des []), il y a moins d'indirections, mais moins de services aussi, donc plus de boulot. On peut aussi chercher une solution plus dédiée comme ici: https://www.boost.org/doc/libs/1_62_0/libs/smart_ptr/shared_array.htm

        • 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.
          10 octobre 2020 à 16:45:59

          Note pour boost sur la dernière version de shared_array: https://www.boost.org/doc/libs/1_74_0/libs/smart_ptr/doc/html/smart_ptr.html#shared_array

          > This facility is deprecated because a shared_ptr to T[] or T[N] is now available, and is superior in every regard.

          • Partager sur Facebook
          • Partager sur Twitter
            11 octobre 2020 à 0:22:52

            Salut,

            Avant toute  chose, j'aimerais te demander quelques précisions, car ta demande laisse trainer beaucoup trop de doutes.

            La première, et sans doute la plus importante de toutes est : pourquoi voudrais tu autoriser la copie (et l'affectation, qui va souvent de pair) de ta classe?

            Il faut comprendre que l'on peut, de manière générale, distinguer deux grandes catégories de classes (ou de structures):

            • Les classes (ou les structures) ayant sémantique de valeur, qui agissent plutôt comme un "ensemble de données qui vont bien ensemble" (comme la notion de point, celle de date, de couleur ou autres), et pour lesquelles il n'y a aucun problème à se retrouver, à un moment précis de l'exécution, avec deux instances présentant des valeurs parfaitement identiques et
            • les classes (ou les structures) ayant sémantique d'entité, pour lesquelles il est "généralement mal venu" de se retrouver, à  un instant donné de l'exécution, de se retrouver avec deux instances présentant exactement les mêmes valeurs (comme la notion de compte en banque, de véhicule ou de bâtiment), "simplement" parce que l'on ne peut alors pas apporter de garantie sérieuse quant au fait que nous apportons effectivement des modifications à "la bonne" instance de la classe ou de la structure.

            Le fait que tu envisages de définir un constructeur de copie semble plaider pour le respect de la forme canonique de Coplien (constructeur, constructeur de copie, opérateur d'affectation et destructeur) et donc pour une sémantique de valeur. 

            Cependant, le fait que tu souhaites que les tableaux de l'instance "originale" et de l'instance "copiées" partagent les mêmes éléments semble plutôt nous placer dans une situation de classe ayant sémantique d'entité.

            Et du coup, si la copie a vraiment du sens, pourquoi voudrais tu que deux instances partagent effectivement le même tableau comme attribut?  Ne serait-il pas plus "logique" que chaque instance de ta classe (que ce soit l'instance "originale" ou l'instance "copiée") puisse manipuler leur "propre copie" des éléments contenus dans le tableau?

            A moins, bien sur, que le contenu  du tableau ne doive vraiment être identique pour les différentes instances de ta classe?  Et, dans ce cas, trois solutions s'offrent à toi:

            • Soit, tu déclare le tableau comme étant statique, c'est à dire "indépendant de toute instance" de ta classe (avec la  restriction qu'il n'y aura jamais plus qu'un seul tableau, qui existera, quelque soit le nombre d'instances originales / copiées de ta classe, et donc, que toute modification de ce tableau apportée à partir de n'importe quelle instance de ta classe sera forcément répercutées sur le tableau de toutes les instances existantes de ta classe)
            • Soit tu déclares l'attribut comme étant une référence sur un tableau existant, avec comme restriction que tu devras veiller à ce que la durée de vie du tableau soit au moins égale à celle de l'instance qui le référencie.
            • Soit, enfin, tu te dis que le tableau n'a en définitive aucun besoin d'être considéré comme un attribut de ta classe, que c'est juste "une information extérieure" qu'il s'agit de fournir à différentes fonctions de ta classe, et tu le transmet "tout simplement" (sous la forme d'une référence, voir d'une référence constante) comme paramètre aux fonctions qui en ont besoin.

            Bon, comme ces explications ne sont pas forcément les plus faciles à comprendre, quelques exemples de code s'imposent:

            Tu pourrais faire en sorte que le même tableau soit utilisé par l'ensemble des instances de ta classes:

            class MaClasse{
            public:
                /* Quelques fonctions pour voir le fonctionnement */
                void printAll() const{
                    for(auto it : items_)
                        std::cout<<it<<"\n";
                }
                void add(int i){
                     items_.push_back(i);
                }
            private:
                static std::vector<int> items_;
            };
            /* utilisation */
            int main(){
                MaClasse obj1;
                MaClasse obj2;
                /* ajouter un élément à obj1 agit aussi sur le contenu
                 * de obj2, et inversement 
                 */
                obj1.add(1);
                obj1.add(2);
                obj1.printall(); // Affiche 1 et 2 sur deux lignes
                obj2.printall(); // affiche aussi 1 et 1 sur deux lignes
                MaClasse obj3{obj1}; // crée une copie de obj1
                obj3.printall(); // affiche encore 1 et 2 sur deux lignes
            }

            Comme on ne sait absolument rien de tes besoins, de ce qu'est sensée faire ta classe, cette méthode pourrait parfaitement avoir du sens... ou non.  Il en va d'ailleurs de même pour les méthodes suivantes.

            Tu pourrais aussi déclarer l'attribut comme étant une référence sur un tableau existant. 

            Cela présentera l'énorme avantage que les copies manipuleront forcément le même tableau que les instances originales.  Par contre, cela implique que les tableau doivent exister au moins aussi longtemps que les instances de ta classes (et leurs copies) sont utilisables:

            class MaClasse{
            public:
                MaClasse(std::vector<int> & tab):
                    items_{tab}{
                }
                MaClasse(MaClasse const &) = default;
                MaClasse & opertor=(MaClasse const &) = default;
                void printAll() const{
                    for(auto it : items_)
                        std::cout<< it <<"\n";
                }
                void add(int i){
                    items_.push_back(i);
                }
            private:
                std::vector<int> & items_; // remarque l'esperluette qui fait tout
            };
            /* utilisation */
            int main() {
                std::vector<int> tabObj1; /* tableau partagé par obj1
                                           * et ses copies
                                           */
                MaClasse obj1{tabObj1};
                MaClasse copy1{obj1};     /* une copie de obj1 */
                std::vector<int> tabObj2: /* tableau partagé par obj2
                                           * et ses copies
                                           */
                MaClasse obj2{tabObj2};
                MaClasse copy2{obj2};     /* une copie de obj1 */
                /* obj1 et copy1 partagent le même tableau */
                obj1.add(1);
                copy1.add(2);
                obj1.printAll(); // affiche 1 et 2 sur deux lignes
                copy1.printAll();// idem
                /* obj2 et copy2 partagent le même tableau */
                obj2.add(3);
                copy2.add(4);
                obj2.printAll(); // affiche 3 et 4 sur deux lignes
                copy2.printAll();// idem
             
            }

            Enfin, nous pourrions partir du principe que le tableau n'intervient en aucun cas dans l'état interne de ta classe, et qu'il n'y a donc aucune raison pour en faire un attribut de celle-ci et qu'il s'agit simplement de "données externes" qu'il faut transmettre à "certaines fonctions" qui en ont besoin.

            Cela nous amènerait sans doute à produire un code proche de

            class MaClasse{
            public:
                /* je ne déclare aucune des fonction de la forme
                 * canonique orthodoxe de coplien, le compilateur
                 * en ajoutera automatiquement une version qui me
                 * convient parfaitement
                 */
                void printAll(std::vector<int> const & items) const{
                    for(auto it: items)
                        std::cout<<it<<"\n";
                }
                void add(std::vector<int>& items, int toadd){
                    items.push_back(toadd);
                }
                /* ... */
            };
            /* utilisation */
            int main(){
                /* Les tableaux doivent tous exister "par eux même"
                 * cependant, aucun tableau n'est spécifiquement "raccordé"
                 * à une instance (ou à une copie d'une instance)
                 *particulière de la classe
                 */
                 std::vector<int> tab1;
                 std::vector<int> tab2;
                 MaClasse obj1;        // une instance de MaClasse
                 MaClasse copy1{obj1}; // et sa copie
                 MaClasse obj2;        // une autre instance de MaClasse
                 MaClasse copy2{obj2}; // et sa copie
                 /* Je peux transmettre n'importe quel tableau à n'importe
                  * quelle fonction de n'importe quelle instance (ou 
                  * copie d'instance) de MaClasse
                  */
                obj1.add(tab1, 1);
                copy2.add(tab1, 2);
                obj2.add(tab2, 3);
                copy1.add(tab2, 4);
                obj1.printAll(tab1); /* affiche le contenu de tab1
                                      * soit 1 et 2 sur deux lignes
                                      */
                copy1.printAll(tab1); /* idem */
                obj2.printAll(tab1);  /* idem */
                copy2.printAll(tab1); /* idem */
                obj1.printAll(tab2); /* affiche le contenu de tab1
                                      * soit 3 et 4 sur deux lignes
                                      */
                copy1.printAll(tab2); /* idem */
                obj2.printAll(tab2);  /* idem */
                copy2.printAll(tab2); /* idem */
            }

            Bien sur, les trois cas que je viens d'évoquer n'auront cours que ... si tu décides de donner une sémantique de valeur à ta classe.

            Car, si ta classe devait avoir une sémantique d'entité, les choses seraient beaucoup plus simple, dans le sens où tu serait bien inspiré d'interdire purement et simplement la copie et l'affectation de ta classe, ce qui nous mènerait à un code proche de

            class MaClasse{
            public:
                /* le comportement par défaut implémenté par le compilateur
                 * pour le constructeur par défaut nous convient très
                 * bien
                 */
                MaClasse() = default;
                /* on demande au compilateur d'interdire la copie et
                 * l'affectation
                 */
                MaClasse(MaClasse const &) = delete;
                MaClasse & operator=(MaClasse const &) = delete;
                /* Le comportement par défaut implémenté par le compilateur
                 * pour le destructeur nous convient très bien.
                 * Cependant, il pourrait sans doute être indiqué comme
                 * virtuel, surtout si MaClasse sert de classe de base
                 * à une hiérarchie de classe
                 */
                 /* virtual */ ~MaClasse() = default;
                 void printAll() const{
                     for(auto it: items_)
                         std::cout<<it<<"\n";
                 }
                 void add(int i){
                     items_.push_back(i);
                 }
            private:
                std::vector<int> items_;
            }
            /* utilisation */
            /* !!! ATTENTION !!! CETTE FONCTION NE COMPILERA PAS CAR ELLE OCCASIONNERAIT
             * UNE TENTATIVE DE COPIE DE L'INSTANCE FOURNIE COMME PARAMETRE.
             * OR, LA COPIE EST INTERDITE
             */
            void foo(MaClasse copy){
                copy.add(2);
            }
            /* POUR POUVOIR TRANSMETTRE UNE INSTANCE DE MaClasse, IL FAUT IMPERATIVEMENT
             * LA TRANSMETTRE PAR REFERENCE
             */
            void add(MaClasse & ref, int toAdd){
                ref.add(toAdd);
            }
            /* OU PAR REFERNCE CONSTANTE */
            void print(MaClasse const & ref){
                ref.printAll();
            }
            int main(){
                MaClasse obj;
                add(obj,1);
                add(obj.2);
                print(obj);
            }
            

            Je crois sincèrement, étant donné le peut d'informations dont on dispose, que, tu es face à une situation dans laquelle la sémantique d'entité serait préférable, même si je peux me tromper à ce sujet.

            • Partager sur Facebook
            • Partager sur Twitter
            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              11 octobre 2020 à 7:33:37

              Avoir un attribut qui se réfère à un attribut d'un autre objet, ça ne paraît pas une conception très saine (*) parce qu'en général, les attributs ont vocation à être la propriété exclusive d'un objet.

              Il faudrait mieux revenir au problème qui se pose concrètement, et chercher une vraie solution. La ce n'en est pas une. "Faire pointer le tableau d'une instance vers celui d'une autre instance", ça ne tient pas la route. Ne serait-ce que parce qu'un tableau, ça ne pointe pas.

              (*) a moins qu'on soit dans le cas d'un "sous objet", comme dans les classes emboîtées de java, mais c++ ne sait pas faire ça.

              -
              Edité par michelbillaud 11 octobre 2020 à 7:34:10

              • Partager sur Facebook
              • Partager sur Twitter

              constructeur de recopie pointeur

              × 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