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 :

Problème de généricité avec les templates


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 99
    Par défaut Problème de généricité avec les templates
    Bonjour,

    Pour la conception d'une application d'affichage de données issues de trames réseaux, je souhaiterais faire quelque chose d'assez générique.

    Je suis donc parti sur l'utilisation de templates et ai procédé de la sorte :

    Une classe pour représenter des données de type traces :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class QTraceData
    {
      public :
        quint8 m_type;
        QString m_date;
        QString m_time;
        QString m_file;
        quint32 m_line;
        QString m_msg;
    };
    Un template pour décoder/encoder les données :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <typename T>
    class QDataDecoder
    {
      public :
        virtual ~QDataDecoder() {}
     
        virtual T* decode(bool&, const QByteArray&) const = 0;
        virtual bool encode(const T*, QByteArray&) const = 0;
    };
    Une classe dérivée pour décoder/encoder les traces :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    class QTraceDecoder : public QDataDecoder<QTraceData>
    {
      public :
        virtual QTraceData* decode(bool&, const QByteArray&) const;
        virtual bool encode(const QTraceData*, QByteArray&) const;
    };
    Un template pour représenter le modèle de données :
    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
    template <typename T>
    class QDataTableModel : public QAbstractTableModel
    {
      public :
        QDataTableModel(QObject* parent = 0)
          : QAbstractTableModel(parent) {}
     
        virtual int rowCount(const QModelIndex&) const
        {
          return m_data.size();
        }
     
        virtual bool insertRow(const T* data)
        {
          // insert data in m_data
        }
     
      protected :
        QList<const T*> m_data;
    };
    Une classe dérivée pour représenter le modèle de traces :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class QTraceTableModel : public QDataTableModel<QTraceData>
    {
      public :
        QTraceTableModel(QObject* parent = 0);
     
        virtual int columnCount(const QModelIndex&) const;
        virtual QVariant data(const QModelIndex&, int) const;
        QVariant headerData(int, Qt::Orientation, int) const;
        bool setData(const QModelIndex&, const QVariant&, int);
    };
    Cette partie pour l'instant me plaisait bien, mais voilà, elle entraîne un manque de généricité au niveau du module ou de la façade censée être le point d'entrée pour la communication avec les autres modules.

    J'ai, au départ, une classe pour la façade :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    class QModelFacade : public Common::QFacade
    {
      public :
        QModelFacade() : Common::QFacade() {}
     
      public slots :
        virtual void interpretData(const QByteArray&) = 0; // appelée lors de la réception d'une trame réseau par le module Network
    };
    Ensuite, et c'est ici que ça se gâte , comme je n'arrivais pas à implémenter de façon générique la méthode interpretData avec les templates QDataDecoder et QDataTableModel (car je dois leur définir un type précis), j'ai créé, à contre coeur^^, une classe dérivée pour manipuler QTraceDecoder et QTraceTableModel :
    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
    class QTraceModelFacade : public QIModelFacade
    {
      public :
        QTraceModelFacade();
        ~QTraceModelFacade();
     
      public slots :
        virtual void interpretData(const QByteArray&);
     
      public :
        QTraceTableModelFacade* m_tableModelFacade;
     
      private :
        QTraceDecoder* m_decoder;
    };
    Au passage, je retrouve la même difficulté avec QTraceTableModelFacade, créée faute d'avoir trouvé le moyen de garder une classe générique QTableModelFacade.

    Ce manque de généricité entraîne, pour un changement de type de données, beaucoup de modifications à faire.. Encore devoir refaire une classe pour décoder ou une classe pour le modèle de ces nouvelles données d'accord, mais devoir recréer une classe façade ce n'est pas normal.

    Je ne maîtrise surement pas assez bien les templates pour contourner cette difficulté et c'est pourquoi je me permets de faire appel à vos connaissances .

    Merci

  2. #2
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Bonjour,

    Je n'ai pas tout compris à ton message, mais j'ai l'impression que tu t'emmele les pinceaux en jouant en même temps avec les templates et virtual.

    Typiquement, quel est l'intérêt de QDataDecoder ? Aucun, si ce n'est éventuellement l'illusion de définir proprement une interface et dans le pire des cas introduire des indirections à l'exécution.

    Tu as besoin dans le cas de ton application, ta bibliothèque de définir une notion de décodage et encodage depuis et vers un QByteArray ? Dans ce cas :
    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
     
    namespace N
    {
        template<class T>
        struct Encode;
     
        template<class T>
        struct Decode;
     
        template<>
        struct Encode<QTraceData>
        {
            QByteArray operator()(const QTraceData& data) const
            { /*implémentation*/ }
        };
     
        template<>
        struct Decode<QTraceData>
        {
            QTraceData operator()(const QByteArray& data) const
            { /*implémentation*/ }
        };
    }
    On aurait pu envisager de simple fonction template, mais c'est problématique si tu veux faire une spécialisation partielle par la suite. Utilisation :
    auto array_byte = Encode<QTraceData>()(a_trace );
    auto a_trace = Decode<QTraceData>()(array_byte);
    Après pour la suite de message, il y a à nouveau des héritages de classes templatées avec des virtuals dont je doute de la pertinence, et j'ai aussi un peu de mal à voir l'interaction de la suite avec ta première classe.

    De manière très général :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    template<class>
    struct A
    { virtual void foo(){}; };
     
    struct B {};
     
    struct C : A<B>
    { };
    N'est utile que si il y a d'autre classe que C qui hérité de A<B>, dans le cas contraire le caractère virtuel de foo est très probablement sans intérêt, et si foo était le seul contenu de A (foo et toutes ses consœurs virtuelles) alors c'est A qui perd aussi son intérêt..

  3. #3
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 99
    Par défaut
    Typiquement, quel est l'intérêt de QDataDecoder ? Aucun, si ce n'est éventuellement l'illusion de définir proprement une interface et dans le pire des cas introduire des indirections à l'exécution.
    Effectivement, c'est exactement ce que j'ai voulu faire avec QDataDecoder. Entre-temps, j'ai modifié mon implémentation pour arriver à quelque chose d'assez similaire à ce que tu proposes (même si dans ton cas, tu utilises plutôt des foncteurs et ça a l'avantage de ne pas avoir à définir une méthode decode et encode par défaut) :
    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
    namespace Model
    {
      class QDataDecoder
      {
        public :
          template <typename T>
          T* decode(const QByteArray&) const
          {
            return NULL;
          }
     
          template <typename T>
          bool encode(const T*, QByteArray&) const
          {
            return false;
          }
      };
     
      template<>
      QTraceData* QDataDecoder::decode<QTraceData>(const QByteArray&) const;
      template<>
      bool QDataDecoder::encode<QTraceData>(const QTraceData*, QByteArray&) const;
     
    } // Model
    Après pour la suite de message, il y a à nouveau des héritages de classes templatées avec des virtuals dont je doute de la pertinence, et j'ai aussi un peu de mal à voir l'interaction de la suite avec ta première classe.
    J'ai aussi défini une classe template pour représenter mon modèle QDataTableModel car cette fois-ci, j'avais besoin de le lier à mes données métiers via une liste de données passée en membre privé et comme la connaissance du type facilite beaucoup de choses dans le traitement ultérieur (pas de dynamic_cast à faire sur le type des données etc..), j'ai voulu "templatiser" la classe.

    Après, dés que la classe est templatisée, il est forcément moins évident d'avoir un module avec un QAbstractDecoder et un QAbstractModel et des méthodes virtuelles qui sont les mêmes pour toutes les classes filles car certaines méthodes ont besoin d'un type précis imposé par le template :s.

  4. #4
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Tu pourrais résumer où en est le problème dans son entièreté avec l'ensemble du code associé et l'objectif final ? Je suppose que c'est clair pour toi car tu es dedans, mais j'ai encore du mal à voir l'ensemble et le problème.

    Typiquement le code des différentes classes et un "main" avec une utilisation basique qui va dans le sens de ce que tu veux, même si ça ne compile pas, l'objectif est justement de voir où tu veux aller (et donc indirectement le problème).

  5. #5
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2012
    Messages
    99
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2012
    Messages : 99
    Par défaut
    Oups, désolé Flob90, je vais faire de mon mieux

    Objectif : développer une application de débogage qui, par réception de traces réseaux, peut les afficher dans un tableau et les manipuler.
    Après, je me suis dit "pourquoi pas faire quelque chose de générique qui permettrait d'afficher dans un tableau tout type d'information provenant du réseau ?".

    D'où mes modules :
    Network : pour recevoir les trames et les décoder
    Model : pour représenter le modèle du tableau
    View : pour représenter la vue liée au modèle

    Mon application devrait donc ressembler à quelque chose de la sorte :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void QAppli::init()
    {
      QGraphicFacade* graphicFacade = new QGraphicFacade();
      QModelFacade* modelFacade = new QModelFacade();
      QNetworkFacade* networkFacade = new QNetworkFacade();
     
      // Pour lier le modèle à la vue
      QAbstractView* view = graphicFacade->getView(idView);
      modelFacade->setModelToView(view);
     
      // Pour mettre à jour le modèle sur réception de trames par le module Network
      modelFacade->setModelToNetwork(networkFacade);
    }
    1) Le module Network aura donc pour charge de récupérer les trames, de les décoder, et de proposer les informations à qui le souhaite :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    void QNetworkFacade::QNetworkFacade()
      : m_config(new QNetworkConfig())
      , m_connection(new QNetworkConnection())
      , m_decoder(new QAbsDecoder()) // Pas possible d'instancier l'objet ici
    {
      createConnection();
    }
    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
    // Slot connecté au signal dataRead() de l'objet connection
    // Son rôle est de décoder le buffer et d'envoyer un signal avec les informations à qui veut s'y connecter
    bool QNetwork::interpretData(const QByteArray& buffer)
    {
      if (!m_decoder)
        return false;
     
      // QData est une classe abstraite héritée par tous les types de données qu'on souhaite récupérer
      QData* data = m_decoder->decode(buffer);
     
      if (m_model)
        m_model->insert(data); // ou emit dataDecoded(data) si connection signal/slot
     
      return true;
    }
    2) Le module Model va être associé à une vue et va recevoir les données du réseau.
    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
    void QModelFacade::QModelFacade()
      : m_config(new QModelConfig())
      , m_model(new QAbstractTableModel())
    {
    }
     
    void QModelFacade::setModelToView(QAbstractView* view)
    {
      view->setModel(m_model);
    }
     
    void QModelFacade::setModelToNetwork(QNetworkFacade* networkFacade)
    {
      networkFacade->setModel(m_model); // Ou une connection signal/slot
    }
    Pour le décodeur et le modèle tableau, j'ai donc utilisé des templates pour ensuite spécialiser le type voulu par héritage.
    Il faut bien de toute façon qu'à un moment ou à un autre, j'indique que ce qui m'intéresse pour le moment est de récupérer des traces, mais je ne sais pas quel endroit serait le plus judicieux pour le faire.

    Peut être mettre le decodeur dans une facade à part et le passer au module Network depuis la classe Application ?
    Et pour le modèle, où lui spécifier le type de données qu'il a à afficher ?

    J'espère avoir été plus clair

  6. #6
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Je suis pas un habitué du mvc et du facade, cependant si je comprends bien, la "pierre angulaire" de ce que tu présentes c'est :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    QData* data = m_decoder->decode(buffer);
    Avant toute chose, ce que tu codes c'est une bibliothèque, destiné à un autre codeur, ou une application destiné à un utilisateur final ?

    Ce qui me gène dans cette ligne c'est que rien n'indique vers quel type le buffer doit être décodé. C'est information est-elle connue à la compilation ou uniquement à l'exécution ?
    • Dans le premier cas, elle devrait apparaître sous forme d'argument template dans cette ligne de code.
    • Dans le second cas, il doit y avoir un branchement conditionnel (direct ou indirect) qui se fait soit depuis une information fournie directement par l'application client soit depuis une information fournie dans le début du buffer (début qui devra donc toujours avoir le même encodage)

Discussions similaires

  1. Problème avec les templates (patrons)
    Par bounadalvidal dans le forum Débuter
    Réponses: 3
    Dernier message: 09/04/2011, 07h14
  2. Problème avec les templates.
    Par mondaying dans le forum C++
    Réponses: 5
    Dernier message: 08/03/2011, 19h03
  3. [Xtext] Problème avec les templates pour les mots clé
    Par P1t0u dans le forum Eclipse Platform
    Réponses: 0
    Dernier message: 10/06/2010, 15h53
  4. Problème avec les templates de class
    Par _SamSoft_ dans le forum C++
    Réponses: 8
    Dernier message: 21/08/2008, 10h30
  5. Problème avec les templates
    Par F-fisher dans le forum C++
    Réponses: 7
    Dernier message: 28/06/2008, 16h04

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