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

Affichage des résultats du sondage: Quel est le meilleur emplacement pour ce typedef ?

Votants
7. Vous ne pouvez pas participer à ce sondage.
  • Dans la définition de la classe

    2 28,57%
  • Après la définition de la classe

    1 14,29%
  • Ça dépend…

    4 57,14%
  • Surtout pas dans le fichier d'en-tête !!!

    0 0%
  • C'est inutile, un typedef

    0 0%
C++ Discussion :

Position d'un typedef de conteneur


Sujet :

C++

  1. #1
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut Position d'un typedef de conteneur
    Bonjour,
    J'ai plus une question de « design » qu'un réel problème à vous soumettre.

    Le contexte est simple : une classe dont les instances sont stockées dans un (ou plusieurs) conteneur(s).
    Histoire de faciliter l'utilisation de ce conteneur, faire un « typedef » me paraît une bonne idée.

    Seulement, où est-il le plus judicieux de le placer ?
    Pour commencer, j'aurais tendance à penser que le fichier d'en-tête n'est pas un mauvais emplacement.
    Mais j'hésite entre le définir comme un type interne ou externe.
    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
    #include <vector>
     
    class UneClasse
    {
     
        public:
            // En type interne
            typedef std::vector<UneClasse> Vector;
     
        (...)
     
    }; // class UneClasse
     
     
    // En type externe
    typedef std::vector<UneClasse> UneClasseVector;
    Quitte à avoir les deux…
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <vector>
     
    class UneClasse
    {
     
        public:
            typedef std::vector<UneClasse> Vector;
     
        (...)
     
    }; // class UneClasse
     
    typedef UneClasse::Vector UneClasseVector;

    Ma première idée serait plutôt de faire un type interne…
    N'hésitez pas à laisser des avis commentés et autres remarques constructives !

    NB: Pour les plus rapides, un peu de patience…
    Des précisions arrivent.

  2. #2
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Bon, comme je le disais, voici quelques précisions concernant le projet qui m'amène cette question.

    Pour certaines classes, le conteneur est un attribut membre statique, dans lequel sont stockées toutes les instances.
    En passant, ces classes sont des usines/fabriques.
    Pour d'autres, c'est un attribut membre non statique d'une autre classe, dans lequel sont stockées uniquement les instances liées.

    Les classes manipulent parfois un de ces conteneurs (ou un sous-ensemble), parfois juste un élément.

    Et pour finir, polymorphisme oblige, ce sont plutôt des conteneurs de pointeurs que de conteneurs d'objets…
    Ça change quelque chose par rapport à ma question ?

    [edit]
    Les classes ont une sémantique d'entité.
    [/edit]

  3. #3
    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,

    A vrai dire, j'aurais tendance à déclarer le typedef dans... la classe qui devra contenir les instances de ma classe (c'est une solution que tu as oubliée dans le sondage) .

    J'en profiterai d'ailleurs sans doute pour ajouter un typedef sur les itérateurs utiles , mais je placerai le typedef sur le conteneur en accessibilité privée, alors que je placerai celui sur les itérateurs dans l'accessibilité qui s'y prête le mieux (sans doute publique pour les const_iterator, vraisemblablement privée pour les iterator)
    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
    class MaClasse
    {
        /* le contenu de MaClasse */
    };
    class MonConteneurDeClasse
    {
        typedef std::vector<MaClasse> vector; // pour la facilité uniquement, à
                                              // usage interne
        /* Peut être en accessibilité publique SSI il est opportun de permettre
         * la modification des instances
         * Sinon, il sera sans doute utile à usage interne ;)
         */
        typedef typename vector::iterator iterator; 
        public:
            /* pour autant que l'on veuille récupérer les instances en dehors
             * de MonConteneurDeClasse ;)
             */
            typedef typename vector::const_iterator const_iterator;
            /* par exemple : */
            const_iterator begin() const{return items.begin();}
            const_iterator end() const{return items.end();}
        private:
            vector items;
    }
    Pour justifier mon choix:

    1- Il se peut parfaitement que tu veuilles manipuler les différentes instances de ta classes sous la forme de collections différentes, en fonction des circonstances: un tableau dans une situation donnée, une map dans une autre situation, une liste dans une troisième, une pile, une file ou un set dans d'autres encore.

    La collection que tu choisira ne dépendra pas de la classe dont tu veux gérer les instances, mais bien de la classe qui a la responsabilité de les gérer

    Si tu donnes cette responsabilité à la classe elle-même, tu te trouvera dans une situation dans laquelle tu ne pourrais pas donner (en dehors de toute justification cohérente et tenant la route pour le faire) d'autre responsabilité à ta classe, en application du fameux principe de la responsabilité unique

    Le type de collection que tu utilisera est à considérer purement et simplement comme un détail d'implémentation de la classe qui a pour responsabilité de gérer les instances de ta classe: Afin de respecter Demeter (et toujours, en l'absence de justification cohérente pour ne pas le faire), tu ne devrais pas exposer ce détail d'implémentation en dehors de la classe .

    Il n'y a donc, a priori, aucune raison d'exposer le fait que le conteneur utilisé est de type std::machin_chose, que ce soit sous la forme d'un typedef ou non

    De plus, on ne peut absolument pas exclure le fait que tu décidera peut être, après moultes essais et benchmarks, de changer le type de collection utilisé

    Par contre, on peut envisager le fait que l'un des services que l'on attend de la classe dont la responsabilité est de gérer les instances de la classe soit, justement, de nous permettre d'accéder à ces instances, de préférence sous la forme d'instances constantes, mais, éventuellement aussi sous la forme d'instances modifiables (en fonction de la situation).

    Le fait d'exposer le typedef sur le const_iterator (voire, sur l' iterator) du conteneur, en plus des fonctions begin et end (au minimum) permettra à l'utilisateur d'accéder aux différentes instances contenues et te permettra d'envisager sereinement (sans crainte de casser le code auquel tu n'as pas accès) de changer le conteneur, du moins, dans la mesure où tu garde un conteneur compatible
    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

  4. #4
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Mal lu l'enoncé, mal répondu au sondage. Ma coulpe.

    On ne mets certainement pas le typedef dans la classe : ce n'est pas à la classe de choisir comment elle doit être utilisée. Et si l'utlisateur préfère une deque ? ou une list ? ou un array ? Tu va mettre tous les typedefs dans la classe ?

    La première bonne réponse, c'est que le typedef est à l'extérieur de la classe. Ou exactement, après ?

    Et bien, for logiquement, et comme le C++ nous l'apprends : là ou on en a besoin. Cf. koala01 pour une première réponse.

    Ensuite, on peut considérer le cas où la collection est utilisée de manière régulière un peu partout. Dans ce cas, on va mettre ce typedef dans l'espace de nom des utilisateurs : ainsi, les différents utilisateurs vont utiliser la même description, et non pas redéfinir leur type de collection en interne (ce qui pourrait provoquer des problèmes de comptabilités de types). A noter qu'on va faire aussi un typedef sur au moins iterator et const_iterator.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  5. #5
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Citation Envoyé par koala01 Voir le message
    A vrai dire, j'aurais tendance à déclarer le typedef dans... la classe qui devra contenir les instances de ma classe (c'est une solution que tu as oubliée dans le sondage) .
    Tu vas rire, mais c'est justement en me demandant où placer le typedef d'un conteneur utilisé (uniquement) dans une autre classe que je me suis décidé à créer ce topic…
    Pour le reste, tu m'as donné matière à réfléchir.

    Citation Envoyé par Emmanuel Deloget Voir le message
    Mal lu l'enoncé, mal répondu au sondage. Ma coulpe.
    Ah je vois, c'est toi qui a répondu « Dans la définition de la classe »…

    Citation Envoyé par Emmanuel Deloget Voir le message
    A noter qu'on va faire aussi un typedef sur au moins iterator et const_iterator.
    Pourquoi est-ce si important ?
    Qu'y a-t-il de mal à écrire : « MonConteneur::iterator » ?

  6. #6
    Membre chevronné Avatar de Astraya
    Homme Profil pro
    Consommateur de café
    Inscrit en
    Mai 2007
    Messages
    1 043
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : France

    Informations professionnelles :
    Activité : Consommateur de café
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Mai 2007
    Messages : 1 043
    Points : 2 234
    Points
    2 234
    Par défaut
    Qu'y a-t-il de mal à écrire : « MonConteneur::iterator » ?
    C'est long et moche . ceci couplé avec une fonction standard, ça deviens vite ugly à souhait!
    Homer J. Simpson


  7. #7
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef MonConteneur::iterator MonConteneurIterator
    Ah oui, il n'y a pas photo, c'est beaucoup plus court…

  8. #8
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par Steph_ng8 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef MonConteneur::iterator MonConteneurIterator
    Ah oui, il n'y a pas photo, c'est beaucoup plus court…
    Tu aurais pu l'appeler LaClasseIteratorQuiEstDefinieDansMonConteneur si tu voulais vraiment faire preuve de mauvaise foi

    A moins que la portée de la définition soit celle du nom d'espace, on utilise généralement :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    class X
    {
    public:
      typedef std::vector<Y> collection;
      typedef collection::iterator iterator;
      ... etc
    };
    Le but étant de limiter la casse lorsque le type itérateur est utilisé dans l'interface de X. Personne n'a envie d'écrire X::collection::iterator pour accéder au nom du type. X::iterator est quand même plus court.

    Lorsque la définition a la portée du nom d'espace, il n'y a guère de gain, si ce n'est qu'on spécifie clairement le type. Après tout, rien ne m'empêche de faire :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef MonConteneur::reverse_iterator MonConteneurIterator;
    C'est aussi à ça que ça sert les typedef : ajouter une petite couche d'abstraction lorsque celle-ci a son utilité; pas seulement à créer des alias de nom pour rendre l'utilisation plus facile
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

  9. #9
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Pendant que j'y suis, dans le cas où toutes les instances d'une classes sont allouées dynamiquement et sont stockées dans un conteneur statique de ladite classe (et pas ailleurs), il vaut mieux écrire une fonction statique qui se charge de faire le ménage, et donc que l'utilisateur devra appeler, ou faire en sorte que la variable membre se charge automatiquement du nettoyage lors de sa destruction ? Ou une autre solution ?
    J'imagine que la première méthode n'est pas à privilégier…

    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
    #include <vector>
     
    class une_classe
    {
     
        private:
            typedef std::vector<une_classe *> vector;
            typedef vector::iterator          vector_iterator;
     
     
            static
            vector instances_;
     
     
        public:
            static
            une_classe * new_instance(...)
            {
                (...)
                une_classe *p = new une_classe(...);
                instances_.push_back(p);
                return p;
            }
     
            static
            void delete_all()
            {
                for (vector_iterator it = instances_.begin(), end = instances_.end(); it != end; ++it)
                    delete *it;
                instances_.clear();
            }
     
        protected:
            une_classe(...);
     
            ~une_classe();
     
        (...)
     
    }; // class une_classe
    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
    #include <vector>
     
    class une_classe
    {
     
        private:
            class vector : public std::vector<une_classe *>
            {
     
                public:
                    vector() : std::vector<une_classe *>() {}
     
                    ~vector()
                    {
                        for (iterator it = begin(), end = this->end(); it != end; ++it)
                            delete *it;
                    }
     
            }; // class une_classe::vector
     
            typedef vector::iterator vector_iterator;
     
     
            static
            vector instances_;
     
     
        public:
            static
            une_classe * new_instance(...)
            {
                (...)
                une_classe *p = new une_classe(...);
                instances_.push_back(p);
                return p;
            }
     
        protected:
            une_classe(...);
     
            ~une_classe();
     
        (...)
     
    }; // class une_classe
    [edit]
    Je viens de retrouver l'article qui parle de la destruction des pointeurs d'un conteneur dans la F.A.Q..
    Merci de ne pas pointer du doigt la manière de faire dans les codes ci-dessus.
    [/edit]

    Que se passe-t-il quand l'allocation échoue ?
    Euh… bah je n'en suis pas encore là…
    Je devrais ?
    Ouais, je sais…

  10. #10
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Citation Envoyé par Emmanuel Deloget Voir le message
    Tu aurais pu l'appeler LaClasseIteratorQuiEstDefinieDansMonConteneur si tu voulais vraiment faire preuve de mauvaise foi
    C'est sûr…
    Mais en fait, je pensais surtout au cas où des conteneurs différents contenant le même type peuvent être définis avec la même portée (std::vector et std::set, par exemple).
    Là, il faut désambigüiser.

  11. #11
    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 Steph_ng8 Voir le message
    C'est sûr…
    Mais en fait, je pensais surtout au cas où des conteneurs différents contenant le même type peuvent être définis avec la même portée (std::vector et std::set, par exemple).
    Là, il faut désambigüiser.
    Comme je l'ai dit plus haut, le type de collection utilisé dépendra essentiellement de son utilité au sein de la de la classe...

    tu te trouvera rarement dans une situation dans laquelle tu devra gérer, dans une même classe, à la fois un vector<UnType> et un set<UnType>...

    Et même si c'est le cas, tu n'exposera généralement que l'itérateur (constant) sur le vector OU celui sur le set, l'autre type de collection servant essentiellement à usage interne
    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

  12. #12
    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 Steph_ng8 Voir le message
    Pourquoi est-ce si important ?
    Qu'y a-t-il de mal à écrire : « MonConteneur::iterator » ?
    outre la simplification du code, il faut comprendre que la collection est à classer dans la catégorie des "détails d'implémentation", alors que l'itérateur (certainement constant, potentiellement non constant) est souvent à classer dans ce que l'on est en droit d'espérer obtenir de la part de la classe qui gère la collection d'objets.

    Il y a, en effet, de grandes chances que l'on ait besoin des services offerts par des fonctions telles que begin et end (permettant d'itérer sur l'ensemble du contenu), find ou autres fonctions similaires, qui, classiquement, renverront un itérateur.

    C'est la raison pour laquelle je présente un code qui place le typedef sur le conteneur dans l'accessibilité privée (car il à usage interne uniquement), celui sur le const_iterator en accessibilité publique (car celui là a de grandes chances d'être utilisé) et que je ne me prononce pas sur la place du typedef sur l'iterateur (non constant), car sa position dépendra très certainement de son utilité (privé si à usage interne, publique si on souhaite pouvoir modifier l'objet itéré).
    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

  13. #13
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Ok pour les itérateurs.

    Et je commence à comprendre qu'il vaut mieux encapsuler les conteneurs qui m'intéressent dans des classes qui fournissent uniquement les opérations nécessaires (ajout, suppression, accès, test d'appartenance, parcours…), et qui se chargent du nettoyage si besoin, plutôt que faire de simples typedefs ou dériver d'un conteneur standard.
    Je me trompe ?

    Encore une question. est-ce que l'on peut mixer les déclarations de tels conteneurs internes et privées (utilisés par une seule classe) et globales (utilisés par plusieurs classes), ou est-ce une mauvaise idée ?
    Dans le cas négatif, est-ce une bonne idée de définir les conteneurs dans le même fichier que l'unique classe qui les utilise ?
    Contrairement à Java, le C++ n'impose pas « une classe par fichier » (ou par paire .h/.cpp), mais j'ai cru comprendre que c'était mieux de s'y tenir.

  14. #14
    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 Steph_ng8 Voir le message
    Pendant que j'y suis, dans le cas où toutes les instances d'une classes sont allouées dynamiquement et sont stockées dans un conteneur statique de ladite classe (et pas ailleurs), il vaut mieux écrire une fonction statique qui se charge de faire le ménage, et donc que l'utilisateur devra appeler, ou faire en sorte que la variable membre se charge automatiquement du nettoyage lors de sa destruction ? Ou une autre solution ?
    Tu auras, sans doute, besoin des deux solutions...: une fonction statique qui permet de retirer un( écartd') élément(s) et qui se chargera d'en libérer correctement la mémoire ET de faire en sorte que la mémoire allouée dynamiquement pour les éléments restant soit automatiquement libérée à la fin de l'application (AKA dans le destructeur).

    J'imagine que la première méthode n'est pas à privilégier…
    Il ne faut pas donner la responsabilité du nettoyage final à l'utilisateur, car il y a parfaitement moyen de faire en sorte que ce soit automatique mais il est peut etre intéressant de lui donner l'occasion de retirer un (écart d') élément(s), en veillant à ce que la mémoire allouée à celui (ceux)-ci soit
    Que se passe-t-il quand l'allocation échoue ?
    Euh… bah je n'en suis pas encore là…
    Je devrais ?
    Ouais, je sais…
    Si l'allocation dynamique de mémoire échoue, new lance une exception de type std::bad_alloc:

    Si elle n'est pas interceptée dans la fonction dans laquelle l'allocation a échoué:
    • Toutes les variables non statiques créées dans la fonction dans laquelle new est appelé sans recourir à l'allocation dynamique sont détruites, dans l'ordre inverse de leur création
    • Les pointeurs non statiques sont perdus, ce qui peut provoquer une fuite mémoire s'ils pointent vers une adresse dont la mémoire a été allouée dynamiquement.
    • Si elle n'est pas interceptée dans la fonction appelante, le phénomène se reproduit pour la fonction appelante, et l'exception remonte toute la pile d'appel
    • Si l'exception n'est pas interceptée dans la fonction principale (main), l'application est quittée. Il faut alors espérer que le système d'exploitation sera en mesure de récupérer la mémoire allouée dynamiquement que l'on a perdu (je te rassure: c'est le cas sur PC )

    Citation Envoyé par Steph_ng8 Voir le message
    Ok pour les itérateurs.

    Et je commence à comprendre qu'il vaut mieux encapsuler les conteneurs qui m'intéressent dans des classes qui fournissent uniquement les opérations nécessaires (ajout, suppression, accès, test d'appartenance, parcours…), et qui se chargent du nettoyage si besoin,
    Il est, surtout, intéressant d'avoir une classe dont la seule responsabilité est la gestion de la durée de vie des différentes instances de ta classe, de manière à savoir à qui aller demander ces instance et, surtout, à être sur qu'elle sera la seule à tenter de libérer la mémoire... Cela évitera bien des problèmes liés aux tentatives de double libération de la mémoire
    plutôt que faire de simples typedefs
    Les typedefs seront présents... dans cette classe particulière
    ou dériver d'un conteneur standard.
    Le principe de base est de ne jamais dériver d'un conteneur de la STL, du moins, pas à but polymorphique.

    Le destructeur des conteneur de la STL est publique (donc accessible depuis n'importe où) mais non virtuel, ce qui fait que, si tu travaille sur un pointeur vers le conteneur, ce sera le destructeur du conteneur qui sera appelé, et non celui de la classe dérivée, avec les problèmes que cela peut engendrer de non destruction des parties propres à la classe dérivée:
    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
    /* OK: l'héritage privé est une relation EST-IMPLEMENTE EN TERME DE
     * tu ne peux pas faire passer MonConteneur pour un std::vector<int>
     */
    struct MonConteneur: private std::vector<int>
    {
    };
    /* Eventuellement, A CONDITION de ne travailler que sur des objets connus
     * comme étant des MonConteneur.
     * 
     * tu comprendra cependant qu'il vaut mieux éviter :D
     */
    struct MonConteneur: public std::vector<int>
    {
    };
    /* A PROSCRIRE car on risque d'être tenté d'avoir un 
     * std::vector< std::vector<int>*> quelque part, et que 
     * le delete sur ces pointeurs ne fera pas ce que l'on croit
     */
     */
    struct M1 : public std::vector<int>
    {
        public:
            virtual ~M1(){}
    };
    struct M2 : public std::vector <int>
    {
        public:
            virtual ~M2(){}
    };
    Je me trompe ?
    Pas du tout, modulo les précisions que j'ai apportées

    Encore une question. est-ce que l'on peut mixer les déclarations de tels conteneurs internes et privées (utilisés par une seule classe) et globales (utilisés par plusieurs classes), ou est-ce une mauvaise idée ?
    Emmanuel a répondu à ta question dans sa première intervention

    Je cite:
    Ensuite, on peut considérer le cas où la collection est utilisée de manière régulière un peu partout. Dans ce cas, on va mettre ce typedef dans l'espace de nom des utilisateurs : ainsi, les différents utilisateurs vont utiliser la même description, et non pas redéfinir leur type de collection en interne (ce qui pourrait provoquer des problèmes de comptabilités de types). A noter qu'on va faire aussi un typedef sur au moins iterator et const_iterator.
    Rien n'empêche d'utiliser le typedef global (pour autant qu'il soit accessible pour la classe que tu écris ) dans ta classe, de manière à être sur que ce soit effectivement cette collection qui sera utilisée, mais, rien ne t'empêche non plus, de le redéfinir dans la classe, voir, de définir un typedef sur une autre collection dans la classe, si le typedef global ne correspond pas à nos besoins.

    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
    /* Fonctionne sans problème : un alias de type sur une collection qui 
     * est accessible de manière globale:
     *
     * NOTA: contrairement à Emmanuel, je peux concevoir, dans ce cas, 
     * de ne pas définir les typedefs sur les iterator et const_iterator...
     * il n'apportent pas vraiment grand chose :D
     */
    typedef std::list<UnType> ma_collection;
     
    /* et une classe perso qui l'utilise */
    class Manager
    {
        public:
            ma_collection::const_iterator begin() const{return items.begin();}
            ma_collection::const_iterator end() const {return items.end();}
        private:
            ma_collection items_;
    } ;
    Dans le cas négatif, est-ce une bonne idée de définir les conteneurs dans le même fichier que l'unique classe qui les utilise ?
    Bien sur...

    Et si ce sont des typedefs qui ne doivent réellement être utilisés que pour la classe (comprend: en interne à la classe et par quelques fonctions d'aide à l'utilisation de cette classe qui ne sont même pas exposées), tu peux même parfaitement envisager de le faire dans le fichier *.cpp:
    conteneur.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    class Conteneur
    {
        private:
            std::machin_chose<UnType> items; //j'en ai mare du vector :D
    };
    conteneur.cpp
    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
     
    #include "conteneur.hpp"
    /* des typedef qui ne seront pas connus de l'extérieur */
    typedef std::machin_chose<UnType> conteneur;
    typedef typename conteneur::const_iterator const_iterator;
    typedef typename conteneur::iterator iterator;
    /* une fonction qui ne sera même pas connue à l'extérieur :D */
    void foo(conteneur & c)
    {
        iterator it=/*what ever */
    }
    void bar(const_iterator b, const_iterator e)
    {
        while(b!=e)
        {
        }
    }
    Contrairement à Java, le C++ n'impose pas « une classe par fichier » (ou par paire .h/.cpp), mais j'ai cru comprendre que c'était mieux de s'y tenir.
    Dans l'absolu, oui, dans la pratique, cela peut s'étendre à un(e partie de) module.

    Mais nous parlons à ce moment là de classes et de structures à ce point inter-dépendante qu'aucune ne sera utilisée sans que l'autre ne soit aussi nécessaire.

    Imaginons, par exemple que j'aie une structure "personne" finalement assez simple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    struct Person
    {
        std::string name;
        std::string surname;
    };
    et que, parce que j'en aurai besoin à peu près partout, je décide de créer les foncteurs permettant de trier cette structure:
    1. selon le nom uniquement ( Truc Zoé peut être avant Truc André)
    2. selon le prénom uniquement (Truc André sera avant Bazar Gilbert)
    3. selon le nom, puis le prénom (Truc André sera d'office avant Truc zoé, mais après toute la famille Bazar)
    4. selon le prénom, puis le nom (tous les André arriveront avant toutes les Zoé)
    (et je ne parle pas de ceux qui permettraient un tri inversé ) Ils ressembleront sans doute à quelque chose comme
    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
    struct lessByName
    {
        bool operator()(Person const & first, Person const & second) const
        {
            return first.name<second.name;
        }
    };
    struct lessBySurname
    {
        bool operator()(Person const & first, Person const & second) const
        {
            return first.surname<second.surname;
        }
    };
    struct lessByNameThenSurname
    {
        bool operator()(Person const & first, Person const & second) const
        {
            return first.name<second.name ||
                   (first.name==second.name && first.surname<second.surname);
        }
    };
     
    struct lessBySurNameThenName
    {
        bool operator()(Person const & first, Person const & second) const
        {
            return first.surname<second.surname ||
                   (first.surname==second.surname && first.name<second.name);
        }
    };
    Le tout pouvant être utilisé sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void foo()
    {
        std::vector<Person> tab;
        /* ...*/
        std::sort(tab.begin(),tab.end(),lessByName());
        /* ...*/
        std::sort(tab.begin(),tab.end(), lessBySurnameThenName());
        /*...*/
    }
    Mais aussi sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    typedef std::set<Person, lessByName> NameSortedSet;
    typedef std::set<Person, lesBySurnameThenName> SurnameThenNameSortedSet;
    Je pourrais parfaitement me retrouver avec 5 voir 9 fichiers d'en-tête présentant chacun l'une des structures envisagées.

    Mais cela impliquerait en retour :
    1. Que je dois faire une déclaration anticipée de Person dans chacun des fichiers d'en-tête dédié à un foncteur
    2. que l'utilisateur devrait, s'il souhaite utiliser un (ou pire plusieurs) foncteur(s) veiller à inclure le(s) fichier(s) d'en-tête dans le(s)quel(s) il(s) est (sont) défini(s), et cela deviendrait vite un casse tête
    3. Qu'il faut que person.h soit inclus avant d'essayer d'inclure les fichiers d'en-tête dédiés aux foncteurs (du moins, tel qu'est présenté le code, avec inlining implicite), or, l'ordre des inclusions doit avoir, autant que faire se peut, aucune importance


    Je pourrais aussi décider de séparer chaque structure dans un fichier d'en-tête particulier et de les regrouper dans un fichier d'en-tête plus "global", mais nous restons malgré tout dans la configuration précédente

    Et, comme même si on regroupe tout cela dans un seul fichier (person.h), il reste d'une taille réellement raisonnable et *relativement* simple, on peut réellement se poser la question de savoir pourquoi nous nous priverions de la possibilité de le faire

    NOTA: pour autant que je me souvienne, Java impose uniquement d'avoir une seule classe accessible depuis l'extérieur par fichier: il n'empêche absolument pas d'avoir deux classes dans le même fichier si l'une d'elle n'est pas accessible depuis l'extérieur ... Ou est-ce moi qui me trompe sur ce coup
    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

  15. #15
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Pff…
    J'avais déjà remarqué que tu pouvais être bavard, mais là…
    Mais au moins c'est clair, précis et détaillé…

    Citation Envoyé par koala Voir le message
    Tu auras, sans doute, besoin des deux solutions...: une fonction statique qui permet de retirer un( écartd') élément(s) et qui se chargera d'en libérer correctement la mémoire ET de faire en sorte que la mémoire allouée dynamiquement pour les éléments restant soit automatiquement libérée à la fin de l'application (AKA dans le destructeur).
    OK.
    En fait, c'est ce que je m'étais dit après-coup…

    Citation Envoyé par koala Voir le message
    Si l'allocation dynamique de mémoire échoue, new lance une exception de type std::bad_alloc:
    Euh, en fait je sais très bien ce qu'il se passe lorsqu'une allocation dynamique échoue…
    Je voulais juste signaler que je suis parfaitement conscient que je ne gère pas (encore) ce cas de figure.

    Citation Envoyé par Steph_ng8 Voir le message
    plutôt que faire de simples typedefs
    Je voulais dire se contenter de faire un typedef du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef std::container<mon_type *> mon_conteneur;
    et du coup laisser la responsabilité du nettoyage (la libération des ressources pointées par les pointeurs du conteneur) à l'utilisateur.

    Citation Envoyé par koala Voir le message
    Citation Envoyé par Steph_ng8 Voir le message
    ou dériver d'un conteneur standard.
    Le principe de base est de ne jamais dériver d'un conteneur de la STL, du moins, pas à but polymorphique.
    En fait, depuis que j'ai remarqué que le destructeur des conteneurs standards n'est pas virtuel, ça ne me vient même plus à l'esprit de les dériver dans un but polymorphique.

    Citation Envoyé par Steph_ng8 Voir le message
    est-ce que l'on peut mixer les déclarations de tels conteneurs internes et privées (utilisés par une seule classe) et globales (utilisés par plusieurs classes), ou est-ce une mauvaise idée ?
    Encore une fois, je n'ai pas été assez précis…
    Je me demandais si c'est cohérent de trouver ce genre de déclarations au sein d'un même projet (je mets des typedefs par simplicité) :
    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
    class classe_1
    {
     
        private:
            typedef std::container<classe_1> collection;
     
        (...)
     
    };
     
     
     
    class classe_2
    { (...) };
     
     
     
    class classe_3
    {
     
        private:
            typedef std::container<classe_2> collection;
     
        (...)
     
    };
     
     
     
    class classe_4
    { (...) };
     
     
     
    typedef std::container<classe_4> collection_classe_4;
    Si j'ai bien compris, la réponse semble être positive.

    Pour le reste, merci de toutes ces précisions.
    Et surtout d'avoir pris le temps de les écrire !

  16. #16
    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 Steph_ng8 Voir le message
    Pff…
    J'avais déjà remarqué que tu pouvais être bavard, mais là…
    Et encore, tu n'as peut être pas lu certains morceau d'anthologie
    Mais au moins c'est clair, précis et détaillé…
    Merci, on fait ce qu'on peut
    Euh, en fait je sais très bien ce qu'il se passe lorsqu'une allocation dynamique échoue…
    Je voulais juste signaler que je suis parfaitement conscient que je ne gère pas (encore) ce cas de figure.
    Mais bon, de manière générale, comment voudrais tu gérer ce cas de figure

    Tu n'a que deux solutions possibles:
    • Soit tu laisse l'application planter de manière plus ou moins sauvage,
    • Soit tu fait savoir à l'utilisateur que ça a échoué, et qu'il ne peut plus rajouter d'éléments... Mais dans ce cas, aura-t-il encore intérêt à continuer à travailler

    Je voulais dire se contenter de faire un typedef du genre :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    typedef std::container<mon_type *> mon_conteneur;
    et du coup laisser la responsabilité du nettoyage (la libération des ressources pointées par les pointeurs du conteneur) à l'utilisateur.
    Tu peux le faire (il n'y a rien qui te l'interdit ), mais si la question est "est-il intéressant / prudent de le faire", la réponse varie de "c'est imprudent et donc pas conseillé" à "uniquement si tu peux accorder une confiance aveugle à l'utilisateur"
    En fait, depuis que j'ai remarqué que le destructeur des conteneurs standards n'est pas virtuel, ça ne me vient même plus à l'esprit de les dériver dans un but polymorphique.
    Et tu as bien raison
    Encore une fois, je n'ai pas été assez précis…
    Je me demandais si c'est cohérent de trouver ce genre de déclarations au sein d'un même projet (je mets des typedefs par simplicité) :
    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
    class classe_1
    {
     
        private:
            typedef std::container<classe_1> collection;
     
        (...)
     
    };
     
     
     
    class classe_2
    { (...) };
     
     
     
    class classe_3
    {
     
        private:
            typedef std::container<classe_2> collection;
     
        (...)
     
    };
     
     
     
    class classe_4
    { (...) };
     
     
     
    typedef std::container<classe_4> collection_classe_4;
    • Si j'ai bien compris, la réponse semble être positive.
    • Hum...
    • Le typedef dans classe_1 n'est a priori pas vraiment cohérent, sauf cas particulier du DP composite (il n'y a, a priori, aucune raison que la définition du contenant se trouve dans le contenu )
    • Le typedef dans classe_3 est, a priori, cohérent: il y a sans doute une relation de contenant (classe_3) à contenu (classe_2) qui peut pleinement le justifier
    • Le typedef global d'un conteneur de classe_4 est, a priori, cohérent: tu incite les gens à utiliser un type de collection particulier de préférence à tout autre, et tu limite ainsi les risques d'ambigüité et de conflit
    Nous sommes cependant bien d'accord sur le fait que c'est une réponse générale, totalement indépendante de toute réalité propre à un projet particulier
    Pour le reste, merci de toutes ces précisions.
    Et surtout d'avoir pris le temps de les écrire !
    Mais de rien
    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

  17. #17
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Tu n'a que deux solutions possibles:
    • Soit tu laisse l'application planter de manière plus ou moins sauvage,
    • Soit tu fait savoir à l'utilisateur que ça a échoué, et qu'il ne peut plus rajouter d'éléments... Mais dans ce cas, aura-t-il encore intérêt à continuer à travailler
    Pour l'instant l'application plante sauvagement , bien qu'on n'en soit jamais arrivé à cette extrêmité.
    Je précise que ce n'est pas moi qui ai écrit le code…
    Mais je pense qu'au final je vais intercepter la « std::bad_alloc » dans le main, indiquer « ENOMEM » et quiter brutalement, mais proprement.

    Citation Envoyé par koala01 Voir le message
    Nous sommes cependant bien d'accord sur le fait que c'est une réponse générale, totalement indépendante de toute réalité propre à un projet particulier
    Nous sommes d'accord.

    Citation Envoyé par koala01 Voir le message
    Le typedef global d'un conteneur de classe_4 est, a priori, cohérent: tu incite les gens à utiliser un type de collection particulier de préférence à tout autre, et tu limite ainsi les risques d'ambigüité et de conflit
    Ok.

    Citation Envoyé par koala01 Voir le message
    Le typedef dans classe_3 est, a priori, cohérent: il y a sans doute une relation de contenant (classe_3) à contenu (classe_2) qui peut pleinement le justifier
    C'est ça !

    Citation Envoyé par koala01 Voir le message
    Le typedef dans classe_1 n'est a priori pas vraiment cohérent, sauf cas particulier du DP composite (il n'y a, a priori, aucune raison que la définition du contenant se trouve dans le contenu )
    Euh non, ce n'est pas un composite.
    C'est plutôt une sorte de fabrique qui ne laisse aucun contrôle à l'utilisateur sur la destruction des objets.
    Du coup, il faut bien les stocker quelque part !
    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
    class toto
    {
     
        private:
            typedef std::container<toto> collection;
     
     
        private:
            static
            collection instances_;
     
     
        public:
            static
            toto* new_instance(...);
     
        private:
            toto(...);
     
            ~toto();
     
            toto& operator = (const toto&);
     
        (...)
     
    }; // class toto

  18. #18
    Membre éprouvé Avatar de Steph_ng8
    Homme Profil pro
    Doctorant en Informatique
    Inscrit en
    Septembre 2010
    Messages
    677
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Informatique

    Informations forums :
    Inscription : Septembre 2010
    Messages : 677
    Points : 997
    Points
    997
    Par défaut
    Bon ok, j'avoue, j'ai un peu triché…
    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
    class toto
    {
     
        private:
            typedef std::container<toto*> collection;
     
     
        private:
            static
            collection instances_;
     
     
        public:
            static
            toto* new_instance(...);
     
     
        protected:
            toto(...);
     
            virtual
            ~toto();
     
        private:
            toto(const toto&);
            toto& operator = (const toto&);
     
        (...)
     
    }; // class toto

  19. #19
    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 Steph_ng8 Voir le message
    Bon ok, j'avoue, j'ai un peu triché…
    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
    class toto
    {
     
        private:
            typedef std::container<toto*> collection;
     
     
        private:
            static
            collection instances_;
     
     
        public:
            static
            toto* new_instance(...);
     
     
        protected:
            toto(...);
     
            virtual
            ~toto();
     
        private:
            toto(const toto&);
            toto& operator = (const toto&);
     
        (...)
     
    }; // class toto
    Conceptuellement, cela n'a aucun sens

    Le DP fabrique serait plutôt proche de
    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
    class Base
    {
        public:
            virtual Base * clone() const = 0;
    };
    class Toto : public Base
    {
        public:
            virual Toto * clone() const;
    };
    class Titi : public Base
    {
        public:
            virtual Titi * clone() const;
    };
    class Fabrique
    {
        public:
            static Base * create(/* some id */);
        private:
            std::some_Collection<Base *> items;
            /* éventuellement, on peut avoir les typedefs basés sur 
             * some_collection, tous seraient privés, vu qu'à usage interne
             * uniquement :D
             */
             */
    };
    class Manager
    {
        typedef std::some_collection<Base*> collection;
        public:
            typedef typename collection::iterator iterator;
            typedef typename collection::const_iterator const_iterator;
            void needSomeItem(/* some id */)
            {
               Base* temp=Fabrique::create(/* some id*/);
               items.push_back(temp);
            }
            /* const and (eventually) no const accessors (not shown)*/
        private:
            collections items;
    };
    Comme je te le dis, le seul cas où le typedef aurait un sens serait sans un cadre proche du composite:
    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
    class Base
    {
        protected:
            typedef std::some_collection<Base*> collection;
        public:
            /* typedef typename collection::iterator iterator; */
            typedef typename collection::const_iterator const_iterator;
            virtual const_iterator begin() const = 0;
            virtual const_iterator end() const = 0;
    };
    class Leaf : public Base
    {
        public:
            virtual ~Leaf(){}
            virtual const_iterator begin() const
            {throw LeavesDoNotHaveChildren();}
            const_iterator end() const
            {throw LeavesDoNotHaveChildren();}
    };
    class Node : public Base
    {
        public
            virtual ~Node()
            { 
                struct deleter
                {
                    void operator()(Base* b)
                    {
                        delete b;
                    }
                };
                std::for_each(items.begin(),items.end(),deleter()};
            virtual const_iterator begin() const
            {return items.begin();}
            const_iterator end() const
            {return items.end();}
        private:
            collection items;
    };
    Conceptuellement parlant, c'est le seul cas où l'on envisage sereinement le fait de voir une structure servir de conteneur pour des objets de son propre type

    En effet, la règle de la responsabilité unique ne doit pas être oubliée: si tu décide déjà de donner la responsabilité de la gestion des éléments d'un type donné, tu ne devrais plus, sur base de cette règle, pouvoir donner d'autre responsabilité

    Le pattern composite me semble être le seul cas où l'on puisse apporter une justification cohérente au fait de déroger à cette règle
    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

  20. #20
    Expert confirmé

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2007
    Messages
    1 895
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 48
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Opérateur de télécommunications

    Informations forums :
    Inscription : Septembre 2007
    Messages : 1 895
    Points : 4 551
    Points
    4 551
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Conceptuellement, cela n'a aucun sens
    Histoire que personne ne soit vexé, je vais expliquer pourquoi.

    Supposons une classe X qui fait les choses suivantes :

    1) elle assure une fonctionnalité quelconque, par exemple la multiplication de deux vecteurs de valeurs issues de l'ensemble Kasparov-Bisounour (hyper compliqué; théorie des groupes, etc. A coté, ce qu'a pondu Evariste, c'est du niveau CE1).

    2) elle permet la création de ces unités de multiplication, ainsi que leur destruction.

    3) elle garde trace de touts les unités de multiplication créées.

    La librairie, écrite il y a plus de 18 jours, est entrée en production et s'est écoulée comme des petits pains - principalement parce que la multiplication Kasparov-Bisounours permet de faire des calculs de probabilité importants pour déterminer les chances de gagner une main au poker.

    Histoire de la rendre encore plus puissante, je souhaite maintenant lui adjoindre autre cas: la multiplication de Gauss-Barbelivien-Feynman, une formule similaire mais donnant des résultats différents. Quelles sont mes options ?

    1) je modifie le code "calcul" existant; dans ce cas, je risque d'introduire un bug important. C'est encore pire parce que la testabilité de l'ensemble est faible - j'estime les chances d'une régression comme étant "assez forte". De plus, quid des modifications client ? Est-ce que l'ajout du calcul va avoir un impact sur l'interface propre à la construction/destruction ?

    2) je rajoute une classe Y qui va coexister avec la classe originelle. Problème : fort logiquement, cette classe doit se comporter de la même manière - et c'est notamment à moi de gérer la construction et la destruction. Hors je ne peux pas réutiliser le code de la classe X puisque je ne suis pas dans la classe X. Je me vois donc forcé de modifier la partie "gestion" de la classe X. Avec tous les risques que cela entraîne.

    Qu'est-ce qui se passe, en réalité ? La classe X assure plusieurs fonctionnalités qui sont complètement différentes : elle se comporte comme une collection, comme un générateur, et a un rôle fonctionnel supplémentaire (le calcul mathématique). Chacune de ses fonctionnalités peut évoluer de manière indépendante. Mais, étant liées, elles traînent un défaut important : une modification de l'une de ces fonctionnalités a un impact sur les autres fonctionnalités. Cet impact ne se voir pas nécessairement au niveau du code, mais principalement au niveau des dépendances.

    Prenons la structure suivante : A1 dépends de X et utilise les fonctions de calcul. A2 dépends aussi de X, mais utilise les fonctions de génération. On voit que quelque soit la modification de X, A1 et A2 sont impactée, même si la modification ne les concerne pas. C'est parce que X a plusieurs raisons de changer - ce qu'un autre a choisi d'appeler les responsabilités de X.

    Dès que X a plusieurs responsabilités, le système devient plus rigide. Etendre X peut nécessite de modifier X (on a parlé récemment de OCP, le principe ouvert/fermé : on doit produire un code ouvert à l'extension, mais fermé à la modification. Le fait d'affecter plusieurs responsabilités à une classe entre en conflit avec ce principe). Modifier X peut avoir un impact sur des parties du logiciel qui, normalement, ne devrait pas être impactée, etc. C'est donc globalement une mauvaise chose.

    D'ou le principe de responsabilité unique (SRP : single responsibility principle) dont l'énoncé est le suivant:

    un objet doit avoir une et une responsabilité (= raison de changer).

    Si on en revient maintenant à la classe présentée : étant similaire à X (sauf pour la multiplication K-B) elle possède trois fonctionnalités différentes, qui sont autant de raisons de possibles modifications indépendantes les unes des autres. C'est clairement un risque, c'est donc clairement quelque chose à éviter.

    D'où la petite phrase de koala01.
    [FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
    Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
    Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
    Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.

    Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.

Discussions similaires

  1. Position relative dans un conteneur en position relative
    Par artefact89 dans le forum Mise en page CSS
    Réponses: 1
    Dernier message: 24/11/2010, 17h16
  2. position d'un control sur un conteneur
    Par sefir dans le forum Macros et VBA Excel
    Réponses: 3
    Dernier message: 22/06/2007, 22h47
  3. CSS position relative / conteneur
    Par Invité dans le forum Mise en page CSS
    Réponses: 4
    Dernier message: 12/09/2006, 22h47
  4. position:relative augmente la taille du conteneur ?
    Par Lideln dans le forum Balisage (X)HTML et validation W3C
    Réponses: 6
    Dernier message: 14/08/2006, 14h03
  5. [Opera 7 & 8] Position absolue dans conteneur relatif
    Par Sub0 dans le forum Balisage (X)HTML et validation W3C
    Réponses: 16
    Dernier message: 16/08/2005, 23h16

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