IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

Transfert de unique_ptr d'un vector à un autre


Sujet :

Langage C++

  1. #1
    Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2017
    Messages
    93
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2017
    Messages : 93
    Points : 60
    Points
    60
    Par défaut Transfert de unique_ptr d'un vector à un autre
    Bonjour,

    Voici un exemple dans lequel j'ai trois classes : Card, Player et Game. La classe Player contient un vector de unique_ptr de Card et la classe Game aussi.

    La classe Player dispose d'une méthode addCard qui permet d'ajouter une carte dans la main du joueur.
    La classe Game dispose aussi d'une méthode drawCard qui retourne la première carte du tas après l'avoir retirée.

    On a donc le code suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include <vector>
    #include <memory>
     
    class Card{};
     
    class Player{
      public:
        std::vector<std::unique_ptr<Card>> hand;
     
        void addCard(std::unique_ptr<Card> card);
    };
     
    void Player::addCard(std::unique_ptr<Card> card){
      hand.push_back(card);
    }
     
    class Game{
      public:
        std::vector<std::unique_ptr<Card>> deck;
     
        std::unique_ptr<Card> drawCard();
    };
     
    std::unique_ptr<Card> Game::drawCard(){
      std::unique_ptr<Card> card = std::move(deck.front());
      deck.erase(deck.begin());
      return card;
    }
    Idéalement, j'aimerais bien ajouter directement une carte dans la main du joueur en la piochant dans le tas du jeu :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    // Player player;
    // Game game;
     
    player.addCard(game.drawCard());
    Seulement ce code ne compile pas (notamment avec la fonction addCard), j'ai plusieurs questions :

    - le code de ma fonction drawCard() est-il correct? Le but est de retirer le premier élément du vector et de le retourner par valeur

    - Comment transférer un unique_ptr d'un vector à un autre?

    Merci d'avance, si ce n'est pas clair n'hésitez pas à me poser des questions

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    std::move
    Maintenant, d'un point de vue architecture, est-ce que ça a du sens que le joueur prenne possession de la carte ? Imo la carte devrait appartenir au jeu, et le joueur n'en a qu'un pointeur.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2017
    Messages
    93
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2017
    Messages : 93
    Points : 60
    Points
    60
    Par défaut
    Bonjour,

    Merci pour votre réponse.

    Ok au niveau de l'architecture ça n'a peut être pas beaucoup de sens mais je veux représenter le fait que le joueur pioche une carte depuis la pioche. La carte piochée n'est donc plus dans la pioche mais dans la main du joueur.

    Dans ce cas comment est-ce que je peux le modéliser?

    Le fait que j'utilise des smart pointer (ici unique ptr) est motivé par le fait de ne pas avoir à s'occuper des opérations mémoire mais aussi cela veut dire que le pointeur sur une carte est unique. Si la carte est unique, alors je ne vois pas comment représenter le fait que le joueur en possède une dans sa main. D'accord elle appartient au jeu mais maintenant elle est contenue dans la main du joueur.

    Je tiens aussi à préciser que j'utilise de pointeurs dans cet exemple car j'ai besoin de polymorphisme dans mon application.

    J'ai un peu de mal à représenter ça en C++ contrairement à d'autres langages comme Ruby ou Java

    Merci encore de prendre le temps!

  4. #4
    Expert éminent
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 565
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 565
    Points : 7 648
    Points
    7 648
    Par défaut
    Bonjour,

    Pour transférer d'une collection vers une variable, tu écris correctement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::unique_ptr<Card> card = std::move(deck.front());
    Par contre quand tu écris hand.push_back(card); tu ne transfères pas une variable dans une collection, tu tentes de copier. Ton compilateur doit te le dire. Il oublie de préciser qu'il manque un std::move.

  5. #5
    Membre habitué
    Homme Profil pro
    Inscrit en
    Février 2013
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Canada

    Informations forums :
    Inscription : Février 2013
    Messages : 70
    Points : 146
    Points
    146
    Par défaut
    Citation Envoyé par dalfab Voir le message
    Bonjour,

    Pour transférer d'une collection vers une variable, tu écris correctement :
    Par contre quand tu écris hand.push_back(card); tu ne transfères pas une variable dans une collection, tu tentes de copier. Ton compilateur doit te le dire. Il oublie de préciser qu'il manque un std::move.
    Effectivement, l'opérateur = de std::unique_ptr n'accepte que des rvalues. Les versions qui opéreraient sur les lvalues ont été intentionnellement supprimées avec =delete.

    Depuis C++ 2011, toutes ces formes de l'opérateur = sont permises https://en.cppreference.com/w/cpp/la...ove_assignment et https://en.cppreference.com/w/cpp/la...opy_assignment. L'opérateur = a été surchargé pour effectuer une copie ou un déplacement. Les deux opérateurs peuvent exister simultanément et le programmeur est libre de désactiver avec =delete la ou les versions qui ne lui conviennene pas.

    std::unique_ptr<T> est un pointeur, ce qui peut être rendu très clair avec cette syntaxe std::unique_ptr<int> p1(new int(24)). Avec std::unique_ptr<T>, aucun opérateur de copie ou d'affectation permet d'avoirs deux instances identiques de std::unique_ptr<T> qui sont toutes deux responsable de la destruction du même pointeur.

  6. #6
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Salut,

    De manière générale, je suis tout à fait d'accord avec Bousk: le joueur n'est pas le propriétaire de la carte: au mieux, il en est seulement l'utilisateur.

    Ceci étant dit, on pourrait même aller plus loin, en estimant que le système de distribution des cartes n'est pas non plus le propriétaire des cartes qu'il distribue, qu'il n'en est aussi qu'un utilisateur.

    Il ne faut en effet jamais oublier que le principe de base à respecter en tous temps est le SRP, dont je te donne ici une adaptation:
    Tout nom (groupe nominal) dans l'analyse des besoins doit apparaitre dans le code sous la forme d'un type de donnée ou d'une donnée
    Tout verbe (groupe verbal) dans l'analyse des besoins doit apparaitre dans le code sous la forme d'une fonction
    Et, du coup, il faut faire la distinction entre le jeu de carte et le système qui permet de distribuer les cartes du jeu!

    Tu as donc trois éléments qui devraient entrer en jeu:
    1. le jeu de cartes proprement dit
    2. le "sabot" qui distribue les cartes (même si tu peux l'appeler comme tu veux)
    3. et le(s) joueurs

    Parmi ces trois éléments, seul le jeu proprement dit est le propriétaire légitime des cartes, et c'est donc le seul élément qui puisse, effectivement, les manipuler sous la forme de std::unique_ptr.
    Les deux autres éléments (le sabot et le(s) joueur(s)) n'étant que des utilisateurs des cartes, et, à ce titre, ils devraient ne manipuler les cartes qu'au travers de std::reference_wrapper.

    De cette manière, tu pourras "remplir" le sabot avec "autant de jeux" que tu le souhaites (les sabots de black jack, au casino, contiennent souvent entre trois et cinq jeux entiers !!!), et transférer les cartes du sabot vers le joueur (ou d'un joueur vers un autre) autant que tu veux, sans risquer de modifier la structure même du jeu de carte en lui-même
    je verrais bien une structure proche de celle-ci pour résoudre ton problème:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    /* Pour jouer aux cartes, on a besoin ... de cartes ... */
    class Card{
        /* tu y mets ce que tu veux ici, mais les cartes auront sémantique d'entité
         * car tu as les cartes "classiques", les tarots, et plein d'autres sortes de
         * jeux de cartes ;)
         */
    public:
        Card() = default;
        Card(Card const &) = delete;
        Card & operator = (Card const &) = delete;
        virtual ~Card() = default;
    };
    /* Le système de distribution des cartes */
    class Distributor{
        /* pour la facilité, parce que le nom complet est très long */
        using stack_t = std::vector<std::reference_wrapper<const Card>>;
    public:
        /* lui aussi, il a sémantique d'entité, 
         * même si il n'est pas destiné à être dérivé
         */
        Distributor() = default;
        Distributor(Distributor const &) = delete;
        Distributor& operator = (Distributor const &) = delete;
        ~Distributor() = default;
        /* Le distributeur va agir comme une collection classique,
         * en permettant 
         * - l'ajout
         * - l'accès à celle qui se trouve "en haut" de la pile
         * - la suppression de celle qui se trouve en haut de la pile
         * - la suppression de toutes les cartes
         * - le mélange des cartes qu'il contient (car cela peut être utile :D )
         * - de savoir s'il contient ne serait-ce qu'une carte
         */
        void push(Card const & card){
            stack_.emplace_back(card);
        }
        Card const & top(){
            assert(current_ < stack_.size() && "Distributor is empty!");
            return stack_[current_].get();
        }
        void pop(){
            assert(current_ < stack_.size() && "Distributor is empty!");
            ++ current_;
        }
        void clear(){
            current_ = 0;
            stack_.clear();
        }
        bool empty() const{
            return stack_.empty() || current_ >= stack_.size();
        }
        void shuffle(){
            std::random_device rd;
            std::mt19937 g(rd());
            std::shuffle(stack_.begin(), stack_.end(), g);
        }
    private:
        stack_t stack_;
        size_t current_{0};
    };
    /* Nous avons les "jeux" (paquets) de cartes ...
     * ce sont les propriétaires des cartes qu'ils contiennent
     */
    class CardPack{
        /* par facilité */
        using pack_t = std::vector<std::unique_ptr<Card>>;
    public:
        /* ils ont -- forcément -- sémantique d'entité
         */
        CardPack(); // il construisent les cartes dont ils ont besoin ici
        CardPack(CardPack const &) = delete;
        CardPack & operator = (CardPack const &) = delete;
        virtual ~CardPack() = default;
        /* Tout ce que l'on attend de leur part, c'est d'être en mesure
         * de placer les cartes dans le sabot
         */
        void fillDistributor(Distributor & d){
            assert(!inDistributor_ && "Already put in a distributor!");
            for(auto const & it : pack_){
                d.push(*it.get());
            }
            inDistributor_ = true;
        }
    private:
        bool inDistributor_{false};
        pack_t pack_;
    };
    /* Et, enfin, on a le joueur */
    class Gamer{
        /* par facilité */
        using hand_t  = std::vector<std::reference_wrapper<const Card>>;
    public:
        /* il a -- forcément -- sémantique  d'entité */
        Gamer() = default;
        Gamer(Gamer const &) = delete;
        Gamer & operator = (Gamer const & ) = delete;
        virtual ~Gamer() = default;
        /* il peut piocher une carte dans le sabot */
        bool draw(Distributor & d){
            if(! d.empty()){
                auto const & card = d.top();
                d.pop();
                hand_.emplace_back(card);
                return true;
            }
            return false;
        }
        /* il pourra sans doute faire d'autres choses...
         * avec toutes ses cartes ... à  toi de les définir 
         */
    private:
        hand_t hand_;
    };
     
    /* enfin, il y a le jeu en lui-même, qui est le propriétaire légitime:
     * - de tous les paquets de cartes utilisés
     * - de tous les joueurs en présence
     * - de tous les sabots mis à disposition
     */
    class Game{
    public:
        /*quelques fonctionalités intéressantes */
        /* ajouter un paquet de cartes */
        void addPack(){
            packs_.emplace_back(std::make_unique<CardPack>());
            /* on choisi le distributeur dans lequel placer le paquet créé 
             * (à toi de voir comment faire)
             */
            auto & pack = packs_.front();
            pack->fillDistributor(chosenDistributor);
        }
        /* et bien sur, il doit s'exécuter */
        void run(){
            /* ... */
        }
        /* et tout le reste, bien sur ! */
    private:
        std::vector<std::unique_ptr<Distributor>> distributors_;
        std::vector<std::unique_ptr<CardPack>> packs_;
        std::vector<std::unique_ptr<Gamer>> gamers_;
    };
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  7. #7
    Membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Mars 2017
    Messages
    93
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 25
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Mars 2017
    Messages : 93
    Points : 60
    Points
    60
    Par défaut
    Bonjour,

    Merci pour vos réponses, je commence à mieux comprendre mais je me rends compte que le C++ me perds beaucoup, il y a beaucoup de façons de faire une chose et j'ai du mal à choisir une bonne syntaxe/ convention.

    Je ne connaissais pas le principe SRP donc merci pour la référence!

    J'ai plusieurs questions :

    - Quel est l'utilité de default? (comme dans Card() = default;) je n'ai jamais vu ça

    - La même question pour delete, est-ce que les deux servent à compiler le code tant qu'on a pas réalisé l'implémentation concrète des constructeurs et des méthodes?

    - Est-ce une convention d'utiliser un underscore à la fin des noms des attributs comme distributors_ ? Si oui pourquoi?

    - Pourquoi utiliser la méthode emplace_back de vector à cette ligne :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    packs_.emplace_back(std::make_unique<CardPack>());
    plutôt que push_back?

    - Pourquoi déclarer le type Card const dans le reference wrapper?

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::vector<std::reference_wrapper<const Card>>;
    - Je remarque l'utilisation régulière du mot clé auto. Est-ce une facilité du langage ou un nouveau standard de programmation en C++?

    Merci

  8. #8
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 614
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 614
    Points : 30 626
    Points
    30 626
    Par défaut
    Citation Envoyé par johhry Voir le message
    Je ne connaissais pas le principe SRP donc merci pour la référence!
    Ouais, mais bon ... c'est vraiment une adaptation très personnelle du SRP (Single Responsability Principle ou principe de la responsabilité unique)...

    Ceci dit, on en parle suffisamment sur le forum que pour que tu puisses en trouver une description détaillée. Et, dans le pire des cas, il te restera toujours wikipedia (profites en alors pour te renseigner sur les autres principes SOLID ). Ou même, mon livre (voir ma signature )

    - Quel est l'utilité de default? (comme dans Card() = default je n'ai jamais vu ça

    - La même question pour delete, est-ce que les deux servent à compiler le code tant qu'on a pas réalisé l'implémentation concrète des constructeurs et des méthodes?
    Voilà encore une question simple qui va demander une réponse assez longue, pour être sur que tu en comprends tous les tenants et les aboutissements. Je t'en présente mes excuses par avance

    Reprenons donc les bases:

    James Coplien a déterminé que toute classe, toute structure de données (car tu n'est pas sans savoir que class et struct, c'est exactement pareil en C++ : il n'y a que la visibilité par défaut qui change ) doit forcément présenter quatre fonctions, quatre comportements de base qui sont:
    • un constructeur par défaut (comprends: qui ne prend aucun paramètre)
    • un constructeur de copie
    • un opérateur d'affectation et
    • un destructeur

    C'est en effet le seul moyen "sensé" de s'assurer, quand tu crées une instance de ta classe (ou de ta structure), que l'instance en question sera -- au minimum -- dans un état cohérent.

    D'ailleurs, si tu crées une classe "sans rien dedans" proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class MaClasse{
     
    };
    tu pourras te rendre compte que le compilateur fournit ces quatre éléments, parce que tu peux:
    1. créer une instance de la classe sans lui donner donner de paramètres au constructeur: MaClasse c; est un code qui fonctionne
    2. créer une copie de l'instance en question : MaClasse d{c}; (avec c étant l'instance créée en (1) ) est aussi un code qui fonctionne
    3. affecter la valeur d'une instance à une autre : d = c; (avec c et d ayant été créées en (1) et en (2) fonctionnera lui aussi
    4. détruire les instances créées quand elles deviennent inutiles. Ca, on ne peut pas vraiment s'en rendre compte, mais, dés que l'on croise l'accolade fermante de la portée dans laquelle c et d ont été créées, le destructeur va fonctionner (dans l'ordre inverse de leur déclaration).

    Seuelement, C++ est au moins aussi fainéant que nous, si bien qu'il ne va créer le constructeur par défaut que ... si tu ne lui donne pas de raison de ne pas le faire.
    Ainsi, si tu donne un constructeur qui nécessite des paramètres, par exemple, avec une classe Point ressemblant à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Point{
    public:
        Point(int x, int y) : x_{x}, y_{y}{
        }
        int x() const{
            return x_;
        }
        int y() const{
            return y_;
        }
    private:
        int x_;
        int y_;
    };
    il ne va pas créer le constructeur par défaut, parce que pour lui, il y a déjà un constructeur valide. Si bien que Point p1{1,2}; va fonctionner parce qu'il va utiliser le constructeur prenant deux arguments alors que Point p2; ne va pas fonctionner "simplement" parce que, pour le compilateur, il n'y a pas de constructeur ne prenant aucun paramètres pour la classe Point.

    Et pourtant, un point qui serait -- "par défaut" -- positionné en 0 sur l'axe des X et en 0 sur l'axe des Y serait tout à fait cohérent, et l'on pourrait même se demander pourquoi il faut impérativement donner les deux valeurs si l'on veut que notre point se trouve à ces coordonnées particulières!

    Il en va d'ailleurs généralement de même pour toutes les classe qui ont sémantique de valeur

    Jusqu'en C++11, nous n'avions pas le choix: si l'on estimait utile d'avoir un constructeur par défaut et un constructeur paramétré, il fallait forcément déclarer et implémenter les deux constructeurs dont nous avions besoin.

    Par contre, si tu crées une classe Personne, les instances de cette classe ne pourront être considérées comme "cohérentes" que ... si elle disposent d'un nom et d'un prénom qui ne soient non vide (et qui ne soient pas "inconnu" et "au bataillon" ). Il n'y a donc aucune raison, pour ce genre de classe se retrouve avec au moins "un certain nombre" de données membre dont la valeur ne serait pas définie.

    Et il en va de même pour toutes les classes ayant sémantique d'entité.

    On comprends donc pourquoi le compilateur agit de la sorte, mais il faut avouer que c'est frustrant dans pas mal de cas, parce qu'il existait à l'époque ce que l'on appelait la règle des trois grands (qui est devenue par la suite la règle des cinq grand, quand C++ a ajouté la sémantique de déplacement), qui dit que, si tu as besoin de définir toi-même une des fonctions parmi
    • le constructeur de copie
    • l'opérateur d'affectation
    • le destructeur
    alors, c'est que tu as de très bonnes raison de définir les trois

    Or, même pour les classes ayant sémantique de valeur, il arrive souvent que l'on ait une bonne raison de définir soi-même le constructeur de copie (et donc de fournir une implémentation perso des deux autres).

    Oui, mais... Il suffit que je définisse moi-même le constructeur de copie, qui n'est jamais qu'un constructeur paramétré "un peu spécial" en définitive, vu qu'il prend ... une instance de la classe comme paramètre, pour que le compilateur décide qu'on lui a donné une bonne raison de ne pas fournir le constructeur par défaut. Et ca, c'est particulièrement frustrant, vu que l'on sait parfaitement qu'il était capable de le faire

    Hé bien, la syntaxe = default (attention: le signe = fait partie de l'ensemble ) permet d'indiquer au compilateur que l'on veut malgré tout qu'il fournisse malgré tout le constructeur (ou le destructeur) en lui donnant le comportement qu'il lui aurait normalement donné. C'est à dire que l'on va lui demander explicitement de fournir un constructeur par défaut qui appellera le constructeur par défaut de toutes les données membres, et, quand il s'agit du destructeur, qu'il fournisse une implémentation qui se contentera d'appeler le destructeur de toutes les données membres dans l'ordre inverse de leur déclaration.

    Quant à la syntaxe = delete Hé bien, elle fait exactement le contraire: elle indique au compilateur qu'il ne doit surtout pas fournir les fonctions qui sont marquées ainsi (c'est à dire le constructeur de copie et l'opérateur d'affectation) parce que la classe que l'on défini a sémantique d'entité, et que nous voulons absolument avoir la garantie qu'il n'existera, à une instant T de l'exécution du programme, jamais qu'une seule copie de chaque instance avec des valeurs particulières.
    - Est-ce une convention d'utiliser un underscore à la fin des noms des attributs comme distributors_ ? Si oui pourquoi
    Oui, c'est la convention que j'utilise généralement (à moins que l'équipe dans laquelle je travaille n'en utilise une autre).

    Simplement parce que l'une des convention les plus fréquemment rencontrée, qui consiste à préfixer les données membres d'un m_ (ex: m_distributors) ne me convainc absolument pas: Elle me fait trop penser à de la notation hongroise et, selon le réglage de ton éditeur de texte, il se peut encore que en écrivant un simple "m_", tu te retrouves avec la liste de toutes les données membres, y compris des données inaccessibles parce que privées dans les classes parents, ce qui rend la recherche de la donnée qui t'intéresse en particulier assez longue.

    De même, je n'aime pas les fonction getXXX, même quand elles ont du sens (je ne parlerai donc pas des setXXX ). je préfère les nommer tout simplement xxx.

    Et je trouve donc qu'avoir une donnée membre Type xxx_; qui va bien avec la fonction Type xxx() const présente "un certain charme"
    - Pourquoi utiliser la méthode emplace_back de vector à cette ligne :

    packs_.emplace_back(std::make_unique<CardPack>());

    plutôt que push_back?
    Parce que push_back fait une copie de l'élément qui lui est donné (à moins que l'on ne lui transmette une rvalue reference).

    Or, unique_ptr n'est justement pas copiable :-$. si bien que, pour pouvoir utiliser push_back, il faudrait transformer notre unique_ptr en rvalue reference en utilisant std::move sous une forme qui deviendrait proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    truc.push_back(std::move(std::make_unique<Type>(/* paramètres*/)));
    emplace_back va utiiser ce que l'on appelle le placement new, ce qui se rapproche très fort d'une copie par déplacement et qui pourra se contenter d'un simple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    truc.emplace_back(std::make_unique<Type>(/* paramètres*/));
    Le code est donc simplement moins long, plus simple à écrire (et à comprendre)

    - Pourquoi déclarer le type Card const dans le reference wrapper?

    std::vector<std::reference_wrapper<const Card>>;
    Parce que les cartes n'ont pas à être modifiables quand on y accède depuis le std::vector, tout simplement.

    Tu ne serais pas content si un type venait à changer tous ses deux et ses trois en as, n'est-ce pas
    - Je remarque l'utilisation régulière du mot clé auto. Est-ce une facilité du langage ou un nouveau standard de programmation en C++?
    C'est ce que l'on appelle "l'inférence de type". C'est une fonctionnalité apparue avec la norme C++11 (qui a donc déjà neuf ans au moment d'écrire ces lignes... on peut plus vraiment dire que c'est "nouveau" ) qui consiste, pour faire simple, à dire au compilateur quelque chose du genre de
    Tu es parfaitement en mesure de déterminer le type de la donnée que je vais utiliser (parce qu'elle sera renvoyée par une fonction, a priori), alors, fais moi pas ch...er en m'obligeant à l'écrire, car, personnellement, moi, je me fous pas mal du type réel, tant que la donnée que j'obtiens réagit de manière appropriée à l'usage que j'en aurai
    L'utilisation de l'inférence de type est, réellement, devenue "la norme" dans le sens où on l'utilise de manière quasi systématique à chaque fois que le type réel de la donnée ne nous intéresse pas plus que cela; chaque fois que ce type réel pourrait -- qui sait -- changer dans le temps; chaque fois que le type devient "imbuvable" à écrire.

    Pour te donner une idée, avant C++11, si je voulais parcourir toutes les cartes d'un jeu, j'aurais du écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    for(std::vector<boost::unique_ptr<Card> >::const_iterator iterator= pack_.begin();iterator != pack_.end(); ++ iterator){
        /* il ne fallait pas oublier, en plus, que c'était un itérateur 
         * sur un boost::unique_ptr<CarD> 
         * que je manipulais */
    }
    (j'ai utilisé les unique_ptr de boost, parce que, std::unique_ptr n'existait pas encore à l'époque )

    Le tout, avec le risque que je décide demain de passer d'un std::vector à une std::list pour contenir les cartes (et qui m'aurait, bien sur, obligé à répercuter le changement partout).

    Entre l'inférence de type et les boucles basées sur les intervalles (qui sont tous deux apparus en C++11), je peux désormais écrire ma boucle sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    for(auto const & iterator : pack_){
        /* je n'ai plus qu'à tenir compte du fait que je manipule
         * un std::unique_ptr<Card> ici
         */
    }
    Et, si je décide demain de changer mon std::vector pour une std::list, il n'y aura pas de problème
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  9. #9
    Expert éminent sénior
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 630
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 630
    Points : 10 556
    Points
    10 556
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Oui, c'est la convention que j'utilise généralement (à moins que l'équipe dans laquelle je travaille n'en utilise une autre).
    il y avait 1 histoire comme quoi le compilateur C++, lorsqu'il fait du "name mangling", il ajoutait 1 ou 2 tirets bas avant le nom __member (<- lien wiki en anglais)
    Et donc, si on rajoute des tirets bas aux noms de ces variables/ membres, on le fait après pour ne pas brouiller la cuisine interne.


    Citation Envoyé par koala01 Voir le message
    C'est ce que l'on appelle "l'inférence de type". C'est une fonctionnalité apparue avec la norme C++11
    La signification du mot clef auto a donc changée par rapport aux normes 1998 (C++98) et 2003 (C++03)

  10. #10
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Or, unique_ptr n'est justement pas copiable :-$. si bien que, pour pouvoir utiliser push_back, il faudrait transformer notre unique_ptr en rvalue reference en utilisant std::move sous une forme qui deviendrait proche de

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    truc.push_back(std::move(std::make_unique<Type>(/* paramètres*/)));
    En fait non, std::move() n'est pas nécessaire, std::make_unique<Type>(/* paramètres*/) est déjà une rvalue .

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 4
    Dernier message: 02/08/2007, 13h37
  2. [VBA-E] Transfert de données d'un répertoir à un autre
    Par anisr dans le forum Macros et VBA Excel
    Réponses: 4
    Dernier message: 26/04/2007, 10h55
  3. [VBA]Transfert d'une zone de liste à une autre
    Par Herman dans le forum VBA Access
    Réponses: 2
    Dernier message: 13/04/2007, 14h24
  4. transfert de données d'une table à l'autre
    Par VIRGINIE87 dans le forum Access
    Réponses: 12
    Dernier message: 06/03/2007, 07h48
  5. Réponses: 3
    Dernier message: 12/01/2007, 16h23

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo