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

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

Langage C++ Discussion :

Ordre d'initialisation OO


Sujet :

Langage C++

  1. #1
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut Ordre d'initialisation OO
    Salut !

    Je me pose une question, dont la réponse me semblait évident mais faute de trouver de source m'inquiète.

    Prenons le code suivant:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
     
    class A
    {
       public:
          A() {}
    };
     
    class B
    {
       public:
          B( A& a ) :
             _A( a ) {}
          A& _A;
    };
     
    class AB
    {
       public:
          AB() :
             _B( _A ), // ici l'ordre est inversé, c'est fait exprès
             _A()
          {
          }
     
       A _A;
       B _B;
    };
    Est-ce que ce code est sûr ?
    Est-ce qu'on peut affirmer que _A sera initialisé avant _B et que donc _B._A ne référencera pas une zone mémoire non alloué, ou est-ce que le comportement est indéfini par la norme ?

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

    Ce dont je suis sur, avant de faire la recherche dans la norme, c'est:
    1. les membres d'une classe sont initialisés dans l'ordre de leur déclaration dans le code
    2. avec un niveau d'avertissement suffisant, le compilateur devrait te sortir un avertissement du genre de Attention A est initialisé avant

    Et, pour toute sécurité, voici ce qu'elle dit précisément:
    Citation Envoyé par la norme 12.6.2 §5
    Initialization shall proceed in the following order:
    • First, and only for the constructor of the most derived class as described below, virtual base classes shall be initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base class names in the derived class base-specifier-list.
    • Then, direct base classes shall be initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).
    • Then, nonstatic data members shall be initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).
    • Finally, the body of the constructor is executed.
    Autrement dit, l'ordre dans lequel tu place les membres dans ta liste d'initialisation ne devrait pas intervenir (sauf restriction éventuelle du compilateur)
    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

  3. #3
    Membre expérimenté

    Profil pro
    Inscrit en
    Juin 2006
    Messages
    1 294
    Détails du profil
    Informations personnelles :
    Localisation : Royaume-Uni

    Informations forums :
    Inscription : Juin 2006
    Messages : 1 294
    Points : 1 543
    Points
    1 543
    Par défaut
    Salut,

    Accessoirement donner à quelque chose un nom qui commence par un underscore produit un comportement indéfini, cf. Quels sont les identificateurs interdits par la norme ?.

    MAT.

  4. #4
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Autrement dit, l'ordre dans lequel tu place les membres dans ta liste d'initialisation ne devrait pas intervenir
    Je me permet de reformuler autrement : quelque soit l'ordre de ta liste d'initialisation, les membres sont toujours construits dans le même ordre que leur déclaration dans la classe.
    Ce qui a comme conséquence dans ton exemple que A est construit avant B quelque soit la forme que tu donnes à ta liste d'initialisation.

    En découlent des conseils de bonnes pratiques :
    1/ Il est fortement recommandé de suivre dans les listes d'initialisations l'ordre effectif de construction pour ne pas laisser croire à un relecteur du code qu'il pourrait en être autrement.
    2/ Il faut autant que possible éviter ce genre de dépendance (problème de conception ?).
    3/ Toute dépendance de construction entre les variables membres devra être explicitement commentée à défaut de pouvoir être évitée. Par cette documentation, les lecteurs du code sont avertis qu'il s'agit d'un comportement compris et maîtrisé par le développeur : la fiabilitié est accrue et la maintenance est facilitée.

  5. #5
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Ok, merci pour ces réponses, en fait je demandais juste si le comportement était défini et certain, ou bien si c'était selon le bon vouloir du compilateur.

    Concernant ta dernière remarque, 3DArchi, en fait je pense que j'ai pas le choix.

    Actuellement j'ai, pour le network, plusieurs couches de classes qui ont des dépendances entre eux assez mal fichus, mais je sais pas comment réorganiser...

    classe Socket qui est en réalité une classe thread qui reçoit des données sur un port
    classe PacketProcessor qui se charge de lire et décrypter les données (il les reçoit de Socket) brut, les envois à IncomingPacketManager qui se charge de lire le détails du packet. Possède aussi une méthode "SendPacket" qui permet d'envoyer un packet à un Endpoint, en ayant donc construit son header et crypté.
    classe IncomingPacketManager qui se charge de lire le packet et de le dispatcher à une classe qui en sait plus sur le monde le message correspondant.
    classe MessageManager qui s'occupe des messages et des conséquences pour le monde
    classe OutgiongMessageManager qui reçoit données brut, en créer des packets, qui les envoi ensuite au PacketProcessor pour qu'il puisse les construire et les crypter.

    J'ai donc, un truc comme ça:
    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
     
    /*
     *  Socket
     *    ^
     *    |               
     *    v                           
     * PacketProcessor <---------------
     *    ^                           |
     *    |                           |
     *    v                           |
     * IncomingPacketManager          |
     *    |                           |
     *    v                           |
     * MessageManager                 |
     *    |                           |
     *    v                           |
     * OutgoingPacketManager  ---------
     */
    Et c'est vraiment la cata à organiser les dépendances. Ca a donné un mélange d'aggrégation et de références un peu partout, mais je vois pas comment faire autrement.

    C'est en voulant suivre l'idée de "une classe, un rôle" que j'en suis arrivé là :\

  6. #6
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    C'est encore un peu abstrait pour que je comprenne où tu as ta dépendance de membre.
    Rassures-moi, c'est un graphe de flux de message et pas d'héritage ?

    P.S. : pour Socket/PacketProcessor, as-tu jeté un coup d'oeil à Présentation des principaux design patterns en C++ (décourateur) de David Come ?

  7. #7
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    C'est bien un graph de flux de message et non d'héritage :p (il n'y en a pas du tout en fait, dans ce cas-ci)

    Je ne vois pas en quoi le pattern decorateur m'avançerait en fait. Je vois pas trop ce que je peux en faire dans mon cas.

    Le code entier se trouve sur un svn sur ma machine et certaines parties sont quelque peu délicates, je vais donc m'abstenir de le mettre ne public sur le forum, mais je vais t'envoyer un PM si jamais tu veux y jeter un oeil et m'aiguiller un peu .

  8. #8
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Citation Envoyé par JulienDuSud Voir le message
    C'est bien un graph de flux de message et non d'héritage :p (il n'y en a pas du tout en fait, dans ce cas-ci)

    Citation Envoyé par JulienDuSud Voir le message
    Je ne vois pas en quoi le pattern decorateur m'avançerait en fait. Je vois pas trop ce que je peux en faire dans mon cas.
    Ca peut être pas mal comme conception quand t'as un message qui traverse différentes couches avant d'être envoyé. PacketProcessor crype les données et les envoies à socket qui fait l'envoi ? Avec le pattern décorateur :
    ISocket <- Socket
    ISocket <- Decorateur (ISocket &m_base) <- PacketProcessor
    Ca t'ajoute de la souplesse. Si tu veux rajouter une compression, il suffit d'ajouter une classe décorateur Compression et de la 'chaîner' dans les décorations de ta socket.
    Citation Envoyé par JulienDuSud Voir le message
    Le code entier se trouve sur un svn sur ma machine et certaines parties sont quelque peu délicates, je vais donc m'abstenir de le mettre ne public sur le forum, mais je vais t'envoyer un PM si jamais tu veux y jeter un oeil et m'aiguiller un peu .
    Montre juste la classe où tu as ce soucis et explique pourquoi.

  9. #9
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Ben en gros, l'intialisation de tout ce brol ressemble à ça:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    Engine::Engine() :
        m_ClientMessageHandler(),
        m_Socket( port, m_ClientMessageHandler ),
        m_ServerMessageHandler( m_Socket.m_PacketProcessor ),
        m_ClientMessageProcessor( &m_ServerMessageHandler, m_Socket.m_PacketProcessor.m_Clients ),
        m_Log( "Engine.log" )
    {
        m_ClientMessageHandler.SetClientMessageProcessor(&m_ClientMessageProcessor);
        LOG( "Starting engine..." );
    }
    Ce qui en soi me déplait au plus haut point. Mais mon expérience limitée ne me dit pas comment remplacer ce code par quelque chose de plus naturel :\

  10. #10
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    Ton Engine doit probablement en faire trop. J'ai l'impression qu'il tente d'assembler les sous-composants avec ce qui lui est passé en paramètre. J'ai l'impression que tu devrais réflechir à un médiateur et/ou une fabrique abstraite ?

  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
    A mon sens:
    • il est typiquement faux de dire qu'un socket est un thread...
    • PacketProcessor, IncomingPacketManager et OutgoingPacketManager en font beaucoup trop
    • MessageManager pourrait être un singleton

    Pour être plus clair:

    On ne peut pas décemment dire qu'un socket est un thread.

    Au pire, un socket a une composante thread, mais il s'agit d'une composition et non d'un héritage. (c'est peut être le cas, mais ta phrase laisse supposer le contraire)

    PacketProcessor serait idéalement scindé en deux parties, qui récupéreraient respectivement une responsabilité de IncomingPacketManager et de OutgoingPacketManager:
    • IncomingPacketProcessor : Recoit le message du socket, supprime l'en-tête du packet (après l'avoir vérifiée) et transmet le tout à IncomingPacketManager.

      Si l'en-tête est corrompue, il s'adresse à OutgoingPacketManager pour qu'il envoie une requête afin de récupérer le packet.
    • OutgoingPacketProcessor: Reçoit le message (sans en-tête) de OutgoingPacketManager, ajoute l'en-tête, et transmet le tout au socket pour émission

    IncomingPacketManager traite chaque packet, et les transmet vers PacketManager.

    Si le packet est inconsistant, il s'adresse à OutgoingPacketManager pour qu'il envoie une requete afin de le récupérer

    Packet manager reçoit les paquets traités de IncomingPacketManager pour les transmettre au monde et reçoit les information du monde pour les transmettre à OutgoingPacketManager

    OutgoingPacketManager crée le paquet (sans l'en-tête) à envoyer sur base des informations reçues et le transmet à OutgoingPacketProcessor.

    Au final, tu en arriverais à un diagramme de communication proche de mon petit schéma
    Images attachées Images attachées  
    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
    Membre confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Merci Koala pour cette réponse, or il se trouve qu'à part le fait de scinder PacketProcessor en IncomingPP/OutgoingPP, tu as récité mot pour mot mon architecture actuelle .

    Effectivement, ce n'est qu'une méthode de Socket qui est un thread.
    Incoming/OutgoingPacketManager ne font en réalité rien de plus que ce que tu as décrit, sauf qu'au lieu de récupérer via/d'envers vers 2 objets différents, ils passent tous les 2 par PacketProcessor. (en réalité, c'est le PacketProcessor qui a besoin de IncomingPacketManager, et OutgoingPacketManager qui a besoin de PacketProcessor).

    Si je scinde PacketProcessor, OutgoingPacketManager aura toujours besoin de OutgoingPacketProcessor, ça ne m'avance pas beaucoup dans le sens où IncomingPacketManager ne connait déjà de toute manière pas directement PacketProcessor (c'est l'inverse).

    Le système en soi ne me déplait pas, loin de là, j'en suis même plutôt satisfait ; C'est l'initialisation ou "comment lancer tout ça ensemble" que j'ai du mal à déterminer proprement.

    Pour le médiateur, si je comprend bien, ça sous-entend une classe qui initialiserait chaque composant de manière non-dépendante à qui que ce soit si ce n'est le médiateur lui-même, et qui s'occuperait de dispatcher tous les events au bon endroit, c'est ça ? Ca me plait assez bien, ça a l'air séduisant comme approche mais, le médiateur proposera alors des méthodes à des composants qui n'en ont pas besoin du tout, et je sais pas si poser des "friend" partout est une bonne solution.

  13. #13
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Points : 13 017
    Points
    13 017
    Par défaut
    En fait, mon clavier a un peu fourché. Je pensais plus à un monteur qui aurait au départ la responsabilité de créer les différents objets et de les lier entre eux. Ca isolerait cette complexité uniquement dans cette classe et simplifierait Engine, qui doit probablement orchestrer le tout ensuite.

  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
    Mais, si tu y regarde bien, les messages ne vont plus que dans un sens à chaque fois, hormis pour le système qui sert de médiateur entre le monde et ton système de transmission.

    Ici, j'ai pris l'idée de créer des raccourcis qui permettent de ne pas repasser par le manager, mais tu pourrais aussi très bien, simplement, envisager de travailler "en étoile":

    Tu as, surement, un objet (que je vais nommer "GlobalManager") qui contient, entres autres, le monde de ton jeu (que je vais nommer World, et que je ne présente ici que "pour mémoire"), le gestionnaire de connection (que je vais nommer ConnectionManager), etc... et qui maintient tout cela sous une forme cohérente durant la durée de ton jeu

    Lorsque tu initialise ton gestionnaire global, tu initialise le monde (message 1 du nouveau schema), et le gestionnaire de connexion (messages 2 et 3)

    Lorsque le socket reçoit un message, il le transmet au gestionnaire de connexion (message 4), qui le fait analyser par MessageInterpreter (message 5), qui délègue l'interprétation de l'en-tête à HeaderInterpreter(message 6), puis qui le transmet à ton gestionnaire principal (message 7) en vue de le faire gérer par le monde (message 8)

    De son coté, lorsque le monde a une requête à envoyer sur le réseau, il la transmet au gestionnaire général (message 9), qui la transmet au gestionnaire de connexion (message 10), qui demande à MessageCreator de le mettre en forme avec l'aide de HeaderCreator (message 11 et 12) avant de l'envoyer pour émission au socket (message 13)

    Tu limite donc fortement les responsabilités et les dépendances:

    il y a une dépendance dans les deux sens entre Socket et le gestionnaire de connexion et une autre (au niveau de la connexion) entre le gestionnaire de connexion et le gestionnaire global.

    Evidemment, je ne me suis attaqué ici qu'à la machinerie principale (car le monde transmettra sans doute les informations reçues aux différents objets qu'il contient) qui concerne la transmission sur internet...

    A coté de cela, le gestionnaire principal s'occupera sans doute aussi du gestionnaire de sons et du gestionnaire d'affichage
    Images attachées Images attachées  
    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 confirmé
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Points : 588
    Points
    588
    Par défaut
    Merci pour ces réponses, je pense que je vais prendre la solution de scinder PacketProcessor en Outgoing/Incoming et déplacer ma gestion de connexion vers MessageProcessor.

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

Discussions similaires

  1. [WD18] Ordre d'initialisation des champs
    Par tAKAmAkA dans le forum WinDev
    Réponses: 2
    Dernier message: 02/10/2014, 17h14
  2. Ordre d'initialisation des attributs
    Par Iradrille dans le forum C++
    Réponses: 7
    Dernier message: 20/02/2013, 03h34
  3. Réponses: 16
    Dernier message: 09/08/2010, 11h14
  4. Réponses: 9
    Dernier message: 21/03/2010, 21h42
  5. [] Tri d'un tableau par ordre alphabétique
    Par cafeine dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 17/09/2002, 08h43

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