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

 C++ Discussion :

[C++][Qt] Projet de Développement: Conseils et bonnes pratiques


Sujet :

C++

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Citation Envoyé par Gm7468 Voir le message
    J'avais vu que les variables globales étaient déconseillées, mais mon statut de débutant me faisait encore défaut et cette notion de structure - effectivement très intéressante dans mon cas - me manquait.

    J'ai parcouru certains cours sur les structures, si je comprends bien, c'est en quelque sorte une notion de classe diminuée?
    Puisque la structure a des champs, qui correspondent plus ou moins à des attributs de classe. Les classes ont juste les méthodes en plus.
    Jusque là est-ce exacte?
    En fait, en C++ du moins, la différence entre une classe et une structure est meme encore plus subtile:

    La seule différence qu'il y ait en C++ tient dans la visibilité par défaut des membres et des fonctions membres

    Par défaut (comprend : tant que tu n'as pas indiqué une visibilité (private: protected: ou public: ), les classes ont une visibilité privée et les structures ont une visibilité publique (y compris en ce qui concerne l'héritage )

    A cette différence près, les classes et les structures s'utilisent exactement de la même manière
    Si oui, pourquoi favoriser une structure, plutôt qu'une classe d'objet?
    Est-ce pour l'avantage de la simplicité? (uniquement des champs/attributs et pas les méthodes, et les "problèmes de l'encapsulation?)
    Tout à fait...

    Disons qu'ici, nous serions vraiment dans le cadre d'une structure qui n'aurait absolument aucune autre responsabilité que... de faire tenir toutes les variables qui t'intéressent ensemble, bien qu'elles n'aient sans doute pas d'autres point commun que... de devoir être accessibles d'un bout à l'autre.

    Tu pourrais, bien sur, utiliser une classe, et pousser l'encapsulation au point de placer les différentes variables en accessibilités privée, mais cela t'obligerait à rajouter des accesseurs (getters) et des mutateurs (setters) ce qui contreviendrait, de toutes manières, à la loi demeter.

    Dés lors, autant ne pas mentir sur les termes clairement faire passer cette structure pour ce qu'elle est : un tas de données pas forcément ordonées (a bunch of data )
    Peut-on placer ce qu'on veut dans une structure?
    A savoir, des variables "habituelles", mais aussi par exemple des tableaux, listes, ou maps dynamiques d'objets persos?
    Ouait, strictement tout ce que tu veux, et meme des fonctions, si tu veux

    En utilisant ce fonctionnement, je devrais donc déclarer la structure au début, par exemple dans le premier module.
    Tout à fait
    Pourrais-je créer une fonction initialise(datas), qui initialiserait les champs de cette structure, séparément de la déclaration? Ou faut-il forcément initialiser les champs dès la déclaration?
    Il faut noter qu'une structure peut parfaitement disposer d'un constructeur prenant des arguments

    Par habitude, j'aime que mes variables soient au moins dans un état cohérent et clairement défini lors de leur création.

    Au pire, cela signifie que tu lis / demande les informations nécessaires à l'initialisation de la structure avant de déclarer la variable, mais ce n'est, à bien y réfléchir, pas un mal : cela te permettra de déléguer d'avantage les responsabilités (vu que, de toutes manières, tu devras quand meme lire ou demander ces informations à un moment où à un autre )

    Mais tu peux, effectivement, décider de laisser cette structure dans un état "invalide" jusqu'à ce que tu aies réuni l'ensemble des informations qui lui sont nécessaires

    Cependant, L'habitude qui consiste à faire en sorte que tes objets soient tout de suite valides et cohérents dés le moment de leur déclaration est une habitude qui évite bien souvent des soucis en permettant, justement, de n'avoir pas à se poser la question de "que reste-t-il à faire avant de pouvoir utiliser l'objet"

    Par exemple:
    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
     
    struct MaStructure {
    	int variable1;
    	double variable2;
    	objetx variable3;
    	map<int, objety> variable4;
    };
     
    struct datas; 
     
    initialise(datas)
    {
    variable1 = 2;
    variable2 = 3.1415926535;
    ...
    }
    Ce n'est pas interdit, mais tu pourrais très bien le faire dans le constructeur aussi, surtout pour les constantes (telles que le PI que l'on remarque dans ton code ou le 2, meme si on ne sait pas à quoi il correspond, il semble destiné à être constant )

  2. #22
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Effectivement, mes exemples de variables seraient plus judicieux en tant que constantes, mais l'idée était là.
    Les choses se précisent, et finalement, une structure n'est qu'une classe publique.
    Au lieu de la déclarer "quelque part", un peu n'importe où, je pourrais alors faire, comme pour les classes, un fichier .h et un fichier .cpp pour la définition de la classe? (ou seulement un fichier .h vu le peu code nécessaire...). Et ce fichier serait inclus dès le début, avec la structure crée et initialisée par un constructeur.

  3. #23
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    A priori la technique du fichier .h définissant la structure et du #include dans le main fonctionne.

    Maintenant, comment faire pour passer la variable du type de la structure (créée dans le main), à la suite (fonctions, modules, etc...).

    Suffit-il, dans ces fonctions, d'utiliser le nom de la variable pour qu'elle soit manipulée? Puisque cette dernière est publique.
    Ou faut-il passer par un pointeur vers cette variable de type MaStructure? Et ainsi passer le pointeur à toutes les fonctions qui ont besoin de cette variable?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Personnellement, je transmettrait une référence sur la structure au constructeur des classes qui en ont besoin...

    Cela donnerait quelque chose proche de
    MyStruct.hpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #ifndef MYSTRUCT_HPP
    #define MYSTRUCT_HPP
     
    /* la structure "fourre tout" */
    struct MyStruct
    {
        Type1 blabla;
        Type2 truc;
        Type3 brol;
    };
     
    #endif //MYSTRUCT_HPP
    La première classe du premier module
    Module1.hpp
    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
     
    #ifndef MODULE_1_HPP
    #define MODULE_1_HPP
    // on utilise ici la capacité de faire des déclaration anticipées ;)
    struct MyStruct;
    class Module1
    {
        public:
            Module1(MyStruct & structure);
            /* ...*/ 
            void doSomething();
        private:
            MyStruct & structure_;
    };
    #endif // MODULE_1_HPP
    Module1.cpp
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include "Module1.hpp"
    #include "MyStruct.hpp"
    Module1::Module1(MyStruct & structure):structure_(structure){}
     
    void Module1::doSomething()
    {
        /* ...*/
         structure_.truc = /* calcul évolué */ ;
        /*...*/
        int i = structure.brol * structure.blabla /* ...*/ 
        /*...*/
    }
    La délcaration anticipée permet au compilateur de savoir qu'il existe une structure de type MyStruct (selon l'exemple) et de ne pas s'inquiéter outer mesure.

    Cela ne fonctionne cependant que lorsque tu utilises les références (éventuellement constantes) ou les pointeurs

    de plus,le compilateur doit savoir de quoi elle est composée dés le moment où tu veux accéder à son contenu, c'est la raison pour laquelle on inclus le fichier d'en-tête MyStruct.hpp dans le *.cpp

  5. #25
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    D'accord oui.
    Donc le principe est à peu près celui auquel j'avais pensé.
    La structure est définie à part, et on la déclare une seule fois pour tout le code, avant la première fois qu'on a à l'utiliser. Cependant, le fait de la mettre private ne va pas empêcher de l'utiliser par la suite ailleurs? Ou est-ce que cette entrave est justement contournée par les pointeurs?

    Quelle est la différence entre les références et les pointeurs?
    Puisque dans une utilisation courante (sans polymorphisme etc...), les deux reviennent au même: passer une adresse mémoire. Alors pourquoi utiliser 2 variables (une pour la variable, et une pour le pointeur *), alors qu'avec une référence on a directement uniquement la variable (&)?
    Ou alors je me méprends totalement sur les 2 concepts?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Il faut savoir que, par défaut, le passage d'argument se fait par copie.

    C'est à dire que, si un argument est transmis par valeur ( par exemple comme dans : void foo(Type argument) ), la fonction va utiliser une copie de l'objet transmis, et que les modifications qui pourraient etre apportée à l'argument au sein de la fonction appelée ne seront pas répercutées sur l'objet dans la fonction appelante.

    Ainsi, avec un code 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
    struct Struct1
    {
        int i;
        int j;
    }
    void foo(Struct1 arg)
    {
        std::cout<<"dans foo :";
        arg.i *=2;
        arg.j *=3;
        std::cout<<" arg.i = "<<arg.i<<" "
                 <<"arg.j = "<<arg.j<<std::endl;
    }
    int main()
    {
        Struct1 s1;
        s1.i = 2;
        s1.j = 3;
     
        std::cout<<"avant la fonction :";
        std::cout<<" s1.i = "<<s1.i<<" "
                 <<"s1.j = "<<s1.j<<std::endl;
        foo(s1);
     
     
        std::cout<<"après la fonction :";
        std::cout<<" s1.i = "<<s1.i<<" "
                 <<"s1.j = "<<s1.j<<std::endl;
        return 0;
    }
    affichera
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    avant la fonction : s1.i = 2 s1.j = 3
    dans la foo :arg.i = 4 arg.j = 9
    apres la fonction : s1.i =2 s1.j = 3
    Pour que les modifications apportées à l'objet dans la fonction appelée soient répercutées dans la fonction appelante, il faut que l'argument de la fonction appelée agisse comme "un alias" de l'objet de la fonction appelante.

    Pour ce faire, on dispose de deux possibilités :
    • passer l'objet sous la forme d'une référence (ex void foo(Type & argument))
    • passer l'objet sous la forme d'un pointeur (ex void bar(Type * argument)
    La seule différence (hormis la syntaxe à utiliser) qu'il y a entre les deux solutions, c'est qu'une référence a une garantie d'existence de l'objet référencé, alors que l'objet référencé par un pointeur peut ne pas exister.

    Toutes deux évitent la copie de l'objet passé en argument, mais la référence est un alias (une référence au terme générique du terme ) de l'objet passé en argument, alors que le pointeur est une variable qui contient l'adresse à laquelle se trouve l'objet réellement utilisé

    En C++, on préfère, autant que possible, transmettre les arguments pour lesquels on veut éviter la copie (que ce soit pour que les modifications apportées dans la fonction appelée soient répercutées dans la fonction appelante ou simplement parce que la copie de l'objet est un processus qui peut couter cher en temps et / ou en mémoire) sous la forme de référence, éventuellement constante si l'objet ne doit pas être modifié par la fonction appelante, car, justement, elle présente l'énorme avantage d'offrir la garantie de l'existence de l'objet référencé.

    De plus, les pointeurs sont souvent associés à la gestion dynamique de la mémoire, et l'on a tot fait, avec un pointeur, de se poser la question de savoir s'il faut ou non libérer la mémoire allouée quand on n'a plus besoin de l'objet

    Le conseil qu'il vaut mieux retenir est donc : utilises les références (éventuellement constantes) chaque fois que possible et n'utilises les pointeurs que "quand tu n'as vraiment pas le choix" (par exemple : quand il se peut que l'objet n'existe pas ).

    Dans l'exemple que j'ai donné dans mon intervention précédente, on on crée la variable de type MyStruct le plus tot possible (en fait, suffisamment tot pour pouvoir encore utiliser cette variable une fois que l'ensemble du processus est terminé, histoire, par exemple, de pouvoir faire un affichage final basé sur son contenu) et on la transmet par référence à toute classe qui a besoin de cette structure pour qu'elle puisse la manipuler à son aise

  7. #27
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    D'accord, donc pour toutes mes fonctions qui utilisent des objets déjà existants, il faut que je passe des références, soit "normales" si les objets ont à être modifiés, soit constantes si je ne fais qu'utiliser les variables.

    Et donc en déclarant la fonction comme ceci: void foo(Type & argument), pour utiliser la variable dans la fonction, il faut utiliser & argument ou argument?

  8. #28
    Membre éprouvé
    Avatar de mitkl
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2010
    Messages
    364
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Février 2010
    Messages : 364
    Points : 1 081
    Points
    1 081
    Par défaut
    argument, tout simplement

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Tu dois utiliser void foo(Type & argument)si l'argument passé a vocation à être modifié dans la fonction et void foo(Type const & argument) si l'argument passé n'a pas vocation à etre modifié par la fonction.

    A l'intérieur de la fonction, tu y accèdes, tout simplement, sous la forme de argument

  10. #30
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Merci pour tout!

  11. #31
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Tu dois utiliser void foo(Type & argument)si l'argument passé a vocation à être modifié dans la fonction et void foo(Type const & argument) si l'argument passé n'a pas vocation à etre modifié par la fonction.

    A l'intérieur de la fonction, tu y accèdes, tout simplement, sous la forme de argument
    Une autre question, pour appeler la fonction à partir d'ailleurs, en faisant: foo(argument) c'est bon?
    Car avec foo(& argument), mon compilateur me renvoie une erreur.

    Dans un développement "normal", il faut ainsi toujours passer par référence les arguments d'une fonction?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Citation Envoyé par Gm7468 Voir le message
    Une autre question, pour appeler la fonction à partir d'ailleurs, en faisant: foo(argument) c'est bon?
    Car avec foo(& argument), mon compilateur me renvoie une erreur.
    En effet, le symbole & a l'inconvénient d'avoir deux significations:
    Lors de la déclaration, c'est le symbole représentant une référence (c'est à dire un alias sur une variable existante (ex : foo(UnType /* const */& truc);)

    Lors de l'utilisation, c'est l'opérateur "address of" qui permet de récupérer l'adresse à laquelle se trouve la variable pour l'assigner, par exemple, à un pointeur (par exemple : UnType * ptr = & mavar;)

    L'avantage des références par rapport aux pointeurs est, outre la garantie d'existence de la variable référencée, que cela s'utilise exactement comme la variable elle-même, à la différence près, bien sur, qu'il n'y a pas de copie de la variable, et donc que toute modification apportée à une référence dans la fonction appelée sera répercutée sur la variable dans la fonction appelante.

    Il faut donc écrire ton code sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* soit la fonction suivante */
    void foo(UnType /* const */ & argument); // la fonction attend une référence
                                            // sur un objet de type UnType 
    /* elle serait appelée sous la forme de */
    int main()
    {
        UnType maVariable;
        foo(maVariable);
        return 0;
    }
    Dans un développement "normal", il faut ainsi toujours passer par référence les arguments d'une fonction?
    Presque...

    Pour tous les types "définis par l'utilisateur" (comprend : toute structure, classe ou union), c'est très clairement préférable pour éviter la copie, qui peut êtrtre très couteuse en temps et en mémoire.

    Pour les "types primitifs" par contre (comprend : char, short, int, long, long long, float, double, long double et leur pendant non signé, si d'application), cela n'a pas énormément d'intérêt car la copie est très rapide et ne demande pas beaucoup plus de mémoire que le mécanisme qui est mis en oeuvre pour obtenir une référence.

    Par contre, si tu veux que les modifications apportées à un type primitif dans la fonction appelée soient répercutées sur la variable passée en argument dans la fonction appelante, il peut malgré tout s'avérer utile de le transmettre par référence

  13. #33
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Encore une fois une réponse claire et précise!
    Dans me mil!

    Merci

  14. #34
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Je reviens encore une fois à la charge avec de nouvelles interrogations.

    Au passage, j'en profite pour poser une "méta-question":
    Toutes les interrogations que je poste sont en rapport direct avec le titre du sujet et mon projet, que j'explicite depuis le début. Cependant, elles sont relativement diverses, et parfois très éloignées les unes des autres.
    Si ces disparités sont trop grandes et que la création d'un nouveau post serait nécessaire, faites-le moi savoir


    Revenons au tout début de ce post, où j'avais présenté le but grossier de mon application. Ci-dessous une petite explication pour illustrer la notion de module, bien que ce ne soit pas mon application, les idées sont bien illustrées:
    Imaginons un exemple un peu abstrait mais compréhensible: une chaine de production de pièces de métal quelconques.
    Au départ on a la matière première, un bloc de métal dans une caisse, c'est l'objet Pièce (avec différents attributs poids, matériau...).
    Le but est d'automatiser la production de la pièce, c'est-à-dire toutes les étapes qui vont transformer la matière première pour arriver à la pièce finale.
    On a donc le cycle principal:

    (Début)->|Module 1: Chercher Matière|->|Module 2: Chauffer|->|Module 3: Forger|->|Module 4: Bavures|->|Module 5: Rendu|->(Fin)

    Le Module 1 correspond à l'action, par exemple effectuée par un bras robot articulé, de prendre le bloc de matière première et de le déposer sur le tapis roulant d'amenée au four. Le Module 2 fixe la température du four, la vitesse du tapis. Le M.3 prend le bloc fondu, le place sur une enclume et tape avec un marteau pour former la pièce. Le M.4 peaufine la pièce et le M.5 commande un bras en fin de chaine pour prendre la pièce finale et la placer dans une caisse, rangée.
    Chaque module effectue donc des actions qui font évoluer l'objet (le même objet) au cours du cycle, et bien entendu ces différents modules sont eux décomposés en successions d'actions plus élémentaires (M.1: Détecter la pièce - Commander bras 1 vers la pièce - Commander bras 1 vers le tapis - poser la pièce de telle manière).
    On remarque également que certaines de ces actions élémentaires sont utiles à plusieurs modules: la fonction Commander bras est nécessaire au module 1, mais le sera probablement pour les modules 3 et 5 également, elle sera donc placée à part, dans le "Module Transversal".
    Voilà pour les notions de module, action...


    Vous m'avez aidé jusque là à concevoir l'architecture des fichiers, en utilisant des structures pour les "variables globales" nécessaires partout, notamment les objets manipulés, ainsi que des namespaces pour séparer les fonctions des différents modules qui doivent pouvoir être appelées dans d'autres bouts de code. J'ai à peu près assimiler le tout et d'après les tests effectués, ca a l'air de fonctionner (j'aurais posé des questions sinon, et j'en reposerai encore ).

    Voici mon nouveau problème: l'enchainement des modules/actions.

    Dans un fonctionnement habituel d'un programme (en tout cas pour moi), on a un main, qui contient une succession de fonctions. Chacune d'entre elles peut appeler d'autres sous-fonctions, etc..., mais leur ordre est défini dans le code. C'est-à-dire qu'à l'exécution, une fonction b() placée après une fonction a() ne sera exécutée qu'une fois la fin de la fonction a() atteinte (avec un return ou au bout "}").
    Rassurez moi, est-ce bien cela pour le moment?

    Par conséquent, dans mon application, j'ai un module principal, dans le namespace Principal, qui appelle les fonctions Module1::module1(), Module2::module2() et Module3::module3(). Admettons:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    namespace Principal{
         void principale(){
              Module1::module1();
              Module2::module2();
              Module3::module3();
         }
    }
    Les fonctions modulex() appelant elles-même des sous-fonctions...

    Si ce que j'ai dit tout à l'heure est exact, module2() sera exécutée qu'une fois que module1() sera terminée.
    Cependant, comment "matérialiser" programmatiquement cette fin d'exécution? Comme je l'ai dit tout à l'heure, quand on arrive au return ou au "}", la fonction est terminée, mais en revenant à l'exemple plus haut, pour l'exemple du bras:
    - On envoie un ordre (par exemple par port COM), de déplacement.
    - On arrive alors à la fin de la fonction qui demande le déplacement, cependant, ce dernier, qui prend du temps, n'est pas terminé lui, et donc la fonction suivante serait appelée alors qu'il ne faudrait pas...
    - Il faudrait donc gérer la réponse du port COM du bras disant "je suis arrivé, passe à la fonction suivante".
    Vous voyez le problème?

    Je suis dans un programme C++ utilisant Qt, j'ai donc à disposition le système de signaux/slots. Seulement, comment utiliser cela pour dire au programme par exemple d'envoyer la requête de placement , et d'attendre que celui-ci se termine SANS bloquer le programme (qui doit réagir à la réception de données dans le port), puis de dire à la fonction mère que c'est terminé et qu'on peut passer à la suite?

    J'ai vu qu'il existe une classe QThread, mais qui n'utilise a priori pas les signaux/slots. Son utilisation serait-elle judicieuse ici?

    Mon problème est vraiment de mélanger à la fois le système de signaux/slots sans que cela bloque la réception, tout en empêchant le programme de passer à la suite tant que l'action n'est pas terminée, ou au moins pendant un délai défini (en cas d'erreur lors de l'action ou lors de la communication)...
    Cela fait beaucoup de choses effectivement, mais on avance dans le projet c'est pour ca

    Merci!

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    C'est là que tu vas commencer à apprécier tous les bienfait de la délégation de responsabilités

    Si tu veux bien, reprenons un peu depuis le début, pour être sur que l'on voit les choses de la même manière

    Tu aurais des parties "métier" qui correspondent à tes différents modules:
    • Module1 : s'occupe de préparer la matière première (pesée, mélange, concassage, ... le tout, sans doute subdivisé en différents modules )
    • Module2 : s'occupe d'apporter la matière première au four
    • Module3 : le four (chauffe la matière première)
    • Module4 : s'occupe d'apporter la matière première chauffée à la forge
    • Module5 : la forge
    • Module6 : s'occupe d'apporter la pièce forgée à l'ébarbeuse
    • Module7 : ébarbeuse
    • Module8 : s'occupe d'apporter la pièce finie à l'emballeuse
    • Module9 : s'occupe d'emballer la pièce finie
    • Module10 : s'occupe de ranger la pièce emballée
    Tu remarqueras que j'ai rajouté quelques modules intermédiaires par rapport à ceux que tu prévois, mais cela entre dans le principe de ce que l'on appelle la "délégation des responsabilités"

    L'avantage, c'est que les modules 2, 4, 6 et 8 sont, en réalités, simplement différentes instances d'un module unique

    "Au dessus" de ces modules, tu aurais la partie Qt qui s'occupe de les manipuler:
    • ManipulateurModule1 : s'occupe de manipuler le module1 (éventuellement subdivisé en Manipulateurpesée, Manipulateurmélange, Manipulateurconcassage, ... )
    • ManipulateurModule2 : s'occupe de manipuler le module2
    • ManipulateurModule3 : s'occupe de manipuler le module3
    • ManipulateurModule4 : s'occupe de manipuler le module4
    • ManipulateurModule5 : s'occupe de manipuler le module5
    • ManipulateurModule6 : s'occupe de manipuler le module6
    • ManipulateurModule7 : s'occupe de manipuler le module7
    • ManipulateurModule8 : s'occupe de manipuler le module8
    • ManipulateurModule9 : s'occupe de manipuler le module9
    • ManipulateurModule10 : s'occupe de manipuler le module10
    Le module1 présenteras sans doute des fonctions comme
    • peser
    • mélanger
    • concasser
    • ...
    et pourrait être associé à différents états:
    1. pesageEnCours (au moment où il pèse la matière première)
    2. pesageFini (au moment où il a fini de peser)
    3. melangeEnCours (au moment où il mélange la matière première)
    4. melangeFini (quand il a fini de mélanger)
    5. concassageEnCours (quand il consasse la matière première)
    6. concassageFini(quand il a fini de concasser la matière première)
    7. enAttente (quand il est près à refaire un nouveau cycle (ce qui signifie : quand la matière première qu'il a préparée dans le cycle précédent a été retirée de ses bacs )

    Les modules 2, 4, 6 et 8 auraient des fonctions comme
    • prendre
    • déplacer
    • déposer
    • retourPositionAttente
    et seraient associés à deux états:
    • libre (ils ne sont pas occupés à déplacer quelque chose)
    • enCharge (ils sont en cours de déplacement)

    Le module3 présenterait des fonctions proches de
    • ouverturePorteEntree (pour pouvoir recevoir la matière à chauffer)
    • fermeturePorteEntree (une fois que la matière est dépposée dans le four)
    • preparationALaChauffe (tout ce qui doit être fait pour assurer que la chauffe se passe bien (création de l'atmosphère artificielle, vérification des pressions, des valves, calcul de la température optimale et du temps de chauffe, ...)
    • chauffe
    • ouverturePorteSortie (pour permettre de venir prendre la matière chauffée)
    • fermeturePorteSortie (une fois que la matière chauffée a été sortie)
    et serait associé à des états comme
    • PorteEntreeOuverte
    • PorteEntreeFermee
    • PreparationEnCours
    • ChauffeEnCours
    • PorteSortieOuverte
    • PorteSortieFermee
    • EnAttente (quand il est près à refaire un nouveau cycle (ce qui signifie : quand la matière qu'il a chauffée dans le cycle précédent a été retirée de ses bacs et que la porte de sortie est fermée )
    Les modules 5, 7 et 9 (respectivement la forge, l'ébarbeuse et l'emballeuse )présenterait des fonctions comme
    • travailler
    et serati associé à des états proche de
    • TravailEnCours (tout le processus de forge, d'ébarbage ou d'emballage (selon le cas) )
    • TravailFini (une fois que le processus est fini, mais tant que la pièce est encore dans la machine)
    • EnAttente (quand la pièce est retirée et qu'on attend de recommencer un cycle)

    L'avantage, c'est que Qt utilise un système de signaux et de slots assez intéressant

    Tu peux parfaitement lancer une étape en connectant le signal d'une des partie qui s'occupe d'un module particulier à un slot de la partie qui s'occupe du module suivant ou du module précédent

    Les différentes parties de Qt qui s'occupent de gérer les modules vont envoyer à la partie qui s'occupe du module suivant et / ou à celle qui s'occupe du module précédant des signaux chaque fois que l'état du module change.

    De même, les différentes parties de Qt disposeront d'un certain nombre de slots auxquels viendront se connecter les signaux envoyés par la partie qui s'occupe du module suivant et / ou par celle qui s'occupe du module précédent

    Chaque slot aura pour responsabilité, en fonction de l'état du module précédent et du module suivant, de lancer une fonction du module dont la partie a la charge ou de mettre l'action en question "en attente" .

    Tu pourrais donc avoir un tableau signal émis / réaction 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
    Emetteur | signal       | quand           | recepteur | réaction       | condition (**)
    -------------------------------------------------------------------------------------
    Module2  | lache        | dans module     | module    | lancer process | module pret
    Module4  |              | suivant         | suivant   |   (***)        |
    Module6  |              |                 |           |                |
    Module8  |              |                 |           |                |
    -------------------------------------------------------------------------------------
    Module3  | porte IN     |porte In ouverte | Module2   | déposer        | Module2 en charge 
             | ouverte      |                 |           |                |
    -------------------------------------------------------------------------------------
    Module3  | porte OUT    |porte OUT ouverte| Module4   | prendre        | Module4 en attente 
             | ouverte      |                 |           |                |
    -------------------------------------------------------------------------------------
    Module1  | process fini | module          | module    | prendre        | module et
    Module5  |              | suivant         | suivant   |                |module suivant
    Module7  |              |                 |           |                |pret
    Module9  |              |                 |           |                |
    -------------------------------------------------------------------------------------
    Module2  | pret         | pret            | module    | prendre note   | module pret
    Module4  |              |                 | précédent |                |
    Module6  |              |                 |           |                |
    Module8  |              |                 |           |                |
    NOTA:
    (*)Il n'est pas impossible que d'autres signaux doivent être émis en temps utiles, n'hésite pas à adapter ce tableau à tes besoins réels
    (**)Les conditions sont données à titre indicatif et devront très certainement être adaptées à la réalité du terrain:

    Ainsi, il n'y a sans doute pas de mal à ce que les modules 2, 6 et 8 attendent en charge près du module dans lequel ils doivent déposer leur marchandise que le module ait fini.

    Par contre, il vaut sans doute mieux attendre que la forge soit prête à recevoir la matière à manipuler avant meme d'ouvrir la porte de sortie du four, pour éviter que le four ou la matière ne refroidisse trop.

    En outre, il sera peut etre aussi intéressant de lancer les signaux un peu avant que le module n'atteigne effectivement un état donné, pour prendre en compte les temps de déplacement et optimiser ainsi les temps d'attente
    (***)N'oublie pas que chaque process complet peut etre subdivisé en différentes étapes qui peuvent chaque fois nécessiter un état particulier de la part du module précédant ou du module suivant

  16. #36
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    Tu fais bien de reprendre depuis le début, vu que le projet est assez conséquent et a priori relativement complexe, il est effectivement nécessaire de s'assurer que les propos de chacun soient cohérents.

    Je pense que nous sommes en phase concernant le fonctionnement général, qui est maintenant plutôt clair.

    Concernant les enchaînements, je pensais également à cela. Si je récapitule correctement, j'aurais d'un coté mes fichiers .h/hpp et mes fichiers .cpp qui correspondent à la partie métier, et contiennent les fonctions dans des namespaces, uniquement C++ ou BOOST, et qui décrivent les actions; et d'un autre coté leurs pendant Qt, qui s'occupera de l’enchaînement et du contrôle du bon fonctionnement de la partie métier?

    Maintenant une question plus spécifique au module de communication, qui s'occupera de la réception des données par le port COM.
    Imaginons que le tapis, le four, etc, soient branchés sur le port COM (en réalité il en faudrait probablement plusieurs, mais peu importe). Quand le programme envoie l'ordre "Faire avancer le tapis de 1 mètre", le module du tapis doit prendre le relais, le faire avancer, et renvoyer une réponse de la bonne exécution. Le programme réceptionne donc cette réponse, la traite, et si l'exécution est ok, envoie l'ordre suivant, par exemple ouvrir le four.
    Comment procéder pour cet enchaînement? A nouveau avec les signaux/slots?

    Et maintenant, compliquons un peu le problème, la communication peu être défectueuse, ou présenter des problèmes passagers, ou encore l'action peut ne pas être effectuée correctement. A ce moment, il faut qu'on gère le problème.
    Par exemple, quand on envoie un ordre, toujours l'ordre "Faire avancer le tapis de 1 mètre" par exemple, la première solution pour continuer est de recevoir la réponse "ok". Mais si jamais la réponse contient autre chose, on relance l'ordre par exemple. Ou encore, si jamais la réponse n'est pas reçue au bout de 10 sec, on relance aussi l'ordre.
    Le système de signaux et slots doit devenir sacrément compliqué non?
    Puisqu'à mon avis, il faudrait en plus que je connecte et déconnecte des signaux et des slots lors de l'exécution pour qu'une fois tel signal déclenche tel slot et que si un problème survient, il en déclenche un autre?
    Comment gérer également cette "temporisation", qui n'en est pas une, puisque le programme ne bloque pas pendant cette tempo, il attend la réception, et si cette dernière n'est pas arrivée en bout de tempo, alors tel signal est envoyé.

    Est-ce que le problème exposé est suffisamment clair/pertinent?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Tu dois considérer le système de signal comme un "communication" (j'ai fait ceci", "il m'arrive cela" ou encore "j'ai terminé telle chose") émise par "n'importe qui" en direction de "toute chose concernée".

    A charge pour "la chose concernée" par un signal de s'y connecter afin de pouvoir y réagir

    La réaction à un signal reçu peut, bien évidemment, dépendre du signal en lui-même, mais aussi de l'état dans lequel se trouve la chose qui le reçoit

    Et tu n'est absolument pas limité ni dans le nombre de message qu'un objet peut envoyer, ni dans le nombre d'objets susceptibles de recevoir le message, ni même du point de vue du moment où un message est émis.

    En effet, si un objet dispose d'un signal "jeMeVautre", tu peux décider d'émettre ce signal à n'importe quel moment, dans n'importe quelle fonction de la classe en question par un emit(jeMeVautre);et les objets qui sont susceptibles de réagir à ce signal seront automatiquement tenue au courent

    Alors, reprenons l'exemple du tapis roulant qui amène la matière première au four.

    La partie qui gère le tapis roulant va invoquer la fonction "avancer" du module et peut, par exemple, lancer un timer qui permet de s'assurer d'une réponse adéquate dans les XXX secondes

    Le module va envoyer l'instruction correspondante par le port COM, et va renvoyer le résultat de la commande (si tant est qu'il la reçoive).

    Il y a donc deux solutions: soit la partie qui gère le module obtient une réponse avant que le timer n'ai décrété un timeout, soit elle n'obtient pas cette réponse.

    Dans le premier cas, la partie qui gère le module peut décider d'émettre un signal en fonction de la réponse qu'elle a obtenu, par exemple:
    • un signal "j'attends la suite" si la réponse est positive ou
    • un signal "j'ai eu un problème" si la réponse est négative
    Elle peut aussi décider de faire d'autres choses en fonction du résultat obtenu, bien sur


    Dans le deuxième cas, elle peut réagir de manière diverses, comme, simplement renvoyer l'instruction ou lancer un signal de type "je me vautre" ou encore... vas savoir ce qu'elle peut avoir à faire

    Les signaux qu'elle va émettre, elle va se contenter de les émettre (voir, si besoin, de réagir à un signal qu'elle aurait elle-même émit ), à charge des "parties concernées" par le signal de réagir en conséquence

    Ainsi, le signal "j'attends la suite" sera sans doute connecté à un slot "ferme la porte" de la partie qui gère le four, et ce slot invoquera la fonction adéquate du module, qui enverra l'instruction en question au four par port COM, ainsi que par la partie qui s'occupe du module précédent qui l'interprétera comme un "Go" pour envoyer la suite

    Les signaux "j'ai un problème" et "je me vautre" peuvent, quant à eux, être récupérés d'une part par l'application (de manière générale) qui se chargera, par exemple, d'afficher un message d'erreur et / ou par la partie qui s'occupe des modules de sécurité d'autre part.

    La partie qui s'occupe des modules de sécurité pourrait quant à elle, par exemple, en réaction, parfaitement, mettre en route une lumière rouge sur un poste de commande ou dans une allée ou activer une sirène en fonction de la gravité du problème

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    Une petite précision encore:

    A priori, le seul cas où tu devras déconnecter un signal, c'est si tu décide de détruire l'objet "récepteur" alors que l'objet "émetteur" continue d'exister, pour éviter que le signal ne soit émis vers... quelque chose qui n'existe plus


    Dans l'autre sens, si l'émetteur est détruit alors que le récepteur continue à exister, il n'y a strictement aucun problème : le slot reste simplement ouvert (et sera fermé lors de la destruction du récepteur), meme si aucun signal ne parviendra plus jamais par ce biais

    Autrement, une fois qu'une connexion a été effectuée, il n'y a strictement aucune raison de supprimer cette connexion

    Ce que tu dois simplement faire, c'est, au niveau du slot, vérifier si le récepteur est en état d'avoir la réaction attendue au signal.

    En fonction de l'état du récepteur, tu pourras décider de le faire réagir de la manière attendue ou de le faire réagir de manière à mettre les autres en attente (par exemple, en emettant un signal "n'en jetez plus" )

  19. #39
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2012
    Messages
    87
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2012
    Messages : 87
    Points : 16
    Points
    16
    Par défaut
    D'accord, je vois tout à fait le fonctionnement.

    Le système des signaux et slots est réellement quelque chose de très puissant, mais il me semble quand même que, dans mon cas, cela va être difficile à mettre en oeuvre. Du moins au moins à concevoir, car j'ai une foultitude des signaux et slots qui vont devoir être créés, mais au moins tu m'as apporté la solution

    Je vois également le fonctionnement que tu me proposes. Il ne faut pas que je déconnecte les liaisons, mais que je crée suffisamment de membres dans les classes correspondantes pour gérer tous les cas que je veux, et ainsi émettre les bons signaux quand il faut pour déclencher les bons slots.

    Le timer dont tu parles m'intéresse particulièrement: j'ai fait des recherches et j'en trouve beaucoup qui, d'après ce que j'ai compris, bloquent le programme. Y a-t-il un timer dans Qt qui ne bloque pas les autres processus? Question sous-entendue: est-ce que le QTimer de Qt (qui dispose du signal "c'est fini") bloque le reste de l'exécution?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 632
    Points : 30 708
    Points
    30 708
    Par défaut
    En XP (Xtrem Programming), il y a deux vocables dont tu peux t'inspirer: YAGNI (You Ain't Gonna Need It, ou, si tu préfères, "Tu n'as pas encore besoin de ca") et KISS (Keep It Simple, Stupid, ou, si tu préfères, "Garde cela simple, stupide").

    Il ne sert à rien d'essayer de prévoir tous les cas de figure et tous les signaux qu'une de tes classes est susceptible d'émettre, ni tous les slots qui sont susceptibles de les récupérer

    Contente toi d'émettre un signal de début et un signal de fin (on pourrait dire "contente toi du cas de base") dans un premier temps

    Une fois que tu obtiens quelque chose qui fonctionne, intéresses toi à ce qui peut faire que cela ne fonctionne pas, et, s'il y a une information à faire passer entre les modules, détermine quelle message doit etre envoyé et qui doit le récupérer

    Tu étofferas ta liste de signaux "au fur et à mesure", et sans de casser la tête

    Quant à QTimer, ce n'est pas une simple fonction sleep (qui est effectivement bloquante), et, pour autant que je sache (mais bon, je n'exclue absolument pas de me tromper), QTimer n'est pas bloquant

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

Discussions similaires

  1. Réponses: 0
    Dernier message: 15/07/2014, 22h31
  2. Réponses: 3
    Dernier message: 30/06/2012, 00h03
  3. Cherche conseil Certification + bonnes pratiques
    Par babass77 dans le forum Certifications
    Réponses: 3
    Dernier message: 09/02/2009, 18h42
  4. Bonne pratique, configuration de projets
    Par gailuris dans le forum EDI et Outils pour Java
    Réponses: 7
    Dernier message: 15/12/2005, 11h57

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