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 :

Économiser les recopies autour d'une fonction.


Sujet :

Langage C++

Vue hybride

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

    Informations forums :
    Inscription : Mars 2008
    Messages : 118
    Par défaut Économiser les recopies autour d'une fonction.
    Bonjour,

    Voici le contexte (je travail avec Qt, mais c'est une question plus général au C++):
    J'ai une classe qui possède en attribut un tableau (en l'occurance, un QJsonArray, nommé mJSONData) et une méthode utilisée pour le remplir (setJSONData(à définir)).
    De l'extérieur, j'ai un QVariant qui me fournit les données, dont voici le prototype de la fonction que j'utilise:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    QJsonArray QVariant::toJsonArray() const
    Actuellement, voici le code que j'ai écris:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    ...
    QJsonArray jsonArray = variant.toJsonArray();
    setJSONData( &jsonArray );
    ...
     
    void MaClasse::setJSONData( QJSonArray *jsonData ){
        mJSONData = jsonData;
    }
    Problème: là je compte 2 recopies, en ligne 3 et 9.

    Déjà, première question: est-ce que les compilateurs (en l'occurence gcc) sont-ils suffisament intélligent pour économiser la recopie inutile tout seuls (sachant que jsonArray n'est plus du tout utilisé après)?
    Peut-être que cette syntaxe est plus évidente pour lui?:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    ...
    setJSONData( variant.toJsonArray() );
    ...
     
    void MaClasse::setJSONData( QJSonArray jsonData ){
        mJSONData = jsonData;
    }
    Mais en principe, je compte toujours deux recopie, lors de l'appel de la fonction, puis à l'intérieur.

    Et sinon, quelle syntaxe me permettrait d'éliminer ce travail inutile?

    Au début, j'avais essayé ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    setJSONData( &(variant.toJsonArray()) );
    mais le compilateur me sortait un beau:
    taking adress of temporary [-fpermissive]
    J'ai essayé plusieurs autres synthaxes, mais aucune ne passait.
    J'ai même tenter un truc que je n'avais encore jamais essayer:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    ...
    setJSONData( variant.toJsonArray() );
    ...
     
    void MaClasse::setJSONData( QJSonArray this->mJSONData ){
    }
    En espérant que lors de l'appel de la fonction, il me fasse la recopie directement dans mon attribut
    Mais évidament, le compilateur m'a revoyé dans ma chambre.

    Une méthode serait d'envoyer une référence du variant, et de faire tout le travail à l'intérieur de la méthode, mais je tiens à exposer une méthode qui ne prend qu'un QJsonArray en argument.

    Vous voyer une manière de faire?
    Merci.

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Dans le cas générique :
    Première question avant d'aller plus loin : Est-ce que tu travailles en C++11 ?
    Et question subsidiaire : Est-ce que QJsonArray implémente la move semantic ?

    Dans les cas spécifique Qt :
    Qt utilise pour quasiment toutes ses classes ce qu'ils nomment implicit sharing (du copy-on-write) http://qt-project.org/doc/qt-4.8/implicit-sharing.html
    Du coup, tu devrais ne pas avoir à te préoccuper trop de où cet objet est copié, tant que tu ne le modifies pas...
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Ton pire problème, ce ne sera pas la copie de ton QJsonArray qui sera le cadet de tes soucis avec le code que tu présentes.

    D'abord, ainsi que JolyLoic l'a fait remarquer, Qt utilise énormément le copy-on-write, ce qui évite les copies tant qu'il n'y a pas de modification de l'objet copié (ce qui sera en soi un bon point ).

    Mais, surtout, tu dois être attentif à la durée de vie de tes variables!

    En effet, les fonctions toXXX (ici, on parle de QJsonArray, mais il en va de même pour toutes les autres fonctions ) renvoie un élément du type indiqué par valeur, ce qui est normal étant donné qu'elles renvoient un élément construit par défaut (vide dans le cas d'un QJsonArray) si la donnée sous-jacente n'est pas du type ad-hoc.

    Cela signifie que la variable qui récupère cet objet n'existe que jusqu'à l'accolade fermante de la portée dans laquelle elle est déclarée.

    Autrement dit, cela correspond à
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void foo(QVariant const & variant){
        QJsonArray array = variant.toJsonArray();
        /* ... *
    } // array est détruit quoi qu'il arrive ici
    ou, si tu préfères (ce sera peut être plus explicite ) , voici ce qui se passera avec un code utilisable:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void foo(QVariant const & variant, MaClasse & cl){
        QJsonArray array = variant.toJsonArray();
        cl.setJSONData(array);
    } // CRACK : array est détruit ici
    void bar(){
        MaClasse obj;
        QVariant variant(/* ... */);
        foo(variant, obj);
        obj.someFunctionAccessingTomJSONData(); // BOUM : mJSONData pointe sur une adresse mémoire
                                                // invalide -->erreur de segmentation (dans le meilleur des cas)
    }
    En gros, c'est ce que l'avertissement taking adress of temporary [-fpermissive] essaye de te faire comprendre .

    Il n'y a pas de recette miracle, mais l'idée est que tu puisses respecter suffisamment le SRP (Single Responsability Principle) que pour avoir la certitude que les objets qui manipulent ton QJsonArray connaissent ton objet comme étant de ce type particulier, afin d'éviter de passer par un QVariant qui te ferait perdre cette information primordiale.

    Je ne peux pas terminer cette intervention sans te rappeler une règle très importante de la logique orientée objet, connue sous le nom de loi de Déméter.

    Cette loi s'exprime sous la forme de
    Citation Envoyé par la loi de Déméter
    Si une classe A (la classe MaClasse dans ton exemple) manipule un objet de type B (QJsonArray dans ton exemple), l'utilisateur d'un objet de type A ne devrait pas avoir à connaître le type B pour manipuler son objet de type A.
    Il est peut être (et encore, il faudrait d'avantage d'information pour pouvoir évaluer la pertinence) utile de permettre à l'utilisateur de MaClasse de récupérer une référence constante sur le QJsonArray.

    Mais si tu veux modifier le QJsonArray auquel ta classe fait référence, tu devrais passer par des comportements spécifiques qui "cachent" à l'utilisateur le fait que ta classe manipule un QJsonArray en interne.

    Ce sera d'autant plus vrai si tu te rends compte qu'il n'y a strictement aucune raison pour permettre à l'utilisateur de récupérer une référence constante sur cet objet sous-jacent .

    Pour prendre une exemple plus frappant, imagines 30 secondes que MaClasse soit en réalité la classe Voiture. Tout le monde sait pertinemment bien qu'une voiture dispose, entre autres choses, d'un réservoir pour le carburant.

    Mais, si l'utilisateur de la classe n'est pas un mécanicien, il n'aura jamais besoin d'accéder directement au réservoir en lui-même. La seule connaissance qu'il en aura passera par les différents voyants / cadrans / écrans qui se trouvent sur le tableau de bord et par la trappe de remplissage.

    La voiture devient, grâce à ces différents éléments (qui correspondent à l'interface publique de ta classe) une sorte de "boîte noire" dans lequel se trouve -- peut être (qui sait, il prend peut etre une forme qui n'a rien à voir avec un objet de type Reservoir ) -- un objet de type Reservoir auquel l'utilisateur n'aura jamais besoin d'accéder directement.

    Si tu veux tirer le meilleur parti de la programmation orientée objet, tu dois modifier ton "schéma de pensée" afin d'arriver à réfléchir non plus en termes de données manipulées (le fait que ta classe MaClasse manipule un QJsonArray ou que la classe Voiture manipule un Reservoir), mais bien en terme de services rendus :
    Q : Qu'est ce que ma classe Voiture devra faire
    R : Elle devra, entre autres, me permettre
    1. d'évaluer la distance que je peux encore parcourir avec le carburant qu'il me reste
    2. d'évaluer le "niveau de remplissage" du réservoir
    3. me permettre de rajouter du carburant

    ==>> elle manipule en interne un réservoir, mais les fonctions qu'elle expose seront de l'ordre de
    1. int kmRestantAvantPanneSeche() const
    2. double niveauDeCarburant() const ou encore
    3. void ajouterCarburant(double quantite)
    4. bool isFull() const
    5. bool isEmpty() const
    Dés lors, poses toi les questions suivantes:
    1. Quelle est la responsabilité de MaClasse
    2. Quels services suis-je en droit d'attendre de la part de MaClasse qui seraient relatifs à l'utilisation interne d'un objet de type QJsonArray
    3. Ces services sont-ils tous cohérents par rapport à la responsabilité de MaClasse
    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
    Membre confirmé
    Profil pro
    Inscrit en
    Mars 2008
    Messages
    118
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2008
    Messages : 118
    Par défaut
    Merci de ta réponse, JolyLoic.

    Première question avant d'aller plus loin : Est-ce que tu travailles en C++11 ?
    Aucune idée. Pas de manière explicite en tout cas, je n'utilise aucune des nouvelles synthaxes introduites. Mais je peux sans problème en utiliser, si la réponse est là.
    Pour mon projet, je ne sais pas comment ça marche. Mon compilateur est G++ 4.7.2 et mon IDE est QtCreator 3.0.0. À l'intérieur, je n'y est trouvé aucune option en rapport à C++11, alors je sais pas si il compile d'emblée en c++11 ou si il faut rajouter une option ou quoique ce soit.
    A vrai dire, je suis encore en train de faire mes premiers pas en C++.
    Là c'est pour un projet perso, mais au boulot, nous ne codons qu'en C.


    Dans les cas spécifique Qt : [...]
    Excélent, merci.
    D'ailleurs je me demandais justement dans cette ligne:
    qu'est-ce que devenaient les anciennes données contenue dans 'mJSONData', et si j'avais besoin de les supprimer explicitement avant de procéder à la nouvelle affectation. Avec ça je n'ai plus à me poser ce genre de questions.
    C'est excélent ce mécanisme. Mais je risque de prendre de mauvaises habitudes du coup, et de ne plus utiliser que des passage d'arguements par valeur, pour tout

    Mais une réponse générique m'intéresserait aussi (pour le passage d'arguement). Ça pourrait me servir dans d'autre cas de figures, même au boulot, si la réponse marche aussi en C.

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

    Informations forums :
    Inscription : Mars 2008
    Messages : 118
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    void foo(QVariant const & variant, MaClasse & cl){
        QJsonArray array = variant.toJsonArray();
        cl.setJSONData(array);
    } // CRACK : array est détruit ici
    C'était bien ça mon problème. J'avais bien conscience que les données de 'array' étaient temporaires et allaient être détruite en sortie de 'foo'. Mais une fois passé dans 'setJSONData', c'est bon je les ai eut, elles sont sauvegardé en dur.
    Par contre elles auraient effectivement existé un cours instant dans 'foo', ce qui est parfaitement inutile. J'aurais aimer trouver uns solution pour que 'variant.toJsonArray' me copie directement les données dans le réceptacle finale,
    sans passer par une donnée temporaire, sans exposer "mJSONData" à foo, ni faire passer l'objet variant directement à ma méthode set.
    J'avais bien essayer ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    cl.setJSONData( &(variant.toJsonArray()) )
    mais le compilo me signalait ce problème de durée de vie limité de l'objet temporaire créé ici. Visiblement, à ce niveau là, la durée de vie était trop courte pour lui.

    Citation Envoyé par koala01 Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    obj.someFunctionAccessingTomJSONData(); // BOUM : mJSONData pointe sur une adresse mémoire
                                                // invalide -->erreur de segmentation (dans le meilleur des cas)
    }
    Non, mJSONData est une variable en dure, pas un pointeur. Je l'avais peut-être pas précisé dans mon premier message et je me rends compte maintenant que j'avais écris un bêtise, ligne 8, c'était bien
    qu'il fallait lire, et pas "mJSONData = jsonData;", mais j'avais changer tellement de fois de syntaxe en tâtonnant sans plus réfléchir, qu'à la fin, en remettant le code propre, ça m'avais échappé.
    De plus, j'imaginais bien que Qt avait implémenté la fonction '=' comme il se doit, par copie bit à bit (bon en fin de compte, ils utilisent l'implicit sharing'), donc pour moi, les données sont bien en place à la fin.

    Citation Envoyé par koala01 Voir le message
    Il n'y a pas de recette miracle, mais l'idée est que tu puisses respecter suffisamment le SRP (Single Responsability Principle) que pour avoir la certitude que les objets qui manipulent ton QJsonArray connaissent ton objet comme étant de ce type particulier, afin d'éviter de passer par un QVariant qui te ferait perdre cette information primordiale.
    Pas le choix. J'ai hérité de la classe QAbstractListModel afin de pouvoir envoyer une liste d'objet à ma vue QML.
    Elle implémente les fonctions 'data(role)' et setData(role, valeur) , qui ne manipulent que des QVariant afin d'accèder aux attributs de mes items, représentées par un string pour le nom et une donnée pouvant être de n'importe quelle type.
    Il se trouve que dans mon cas, un des "rôles" est lui-même une QJsonArray, contenant les données d'une liste d'objets de plus bas niveau (qui repasseront eux-aussi par des sous-fonctions data() et setData()).
    Côté C++ j'ai écris une fonction qui parse un fichier JSON pour créé et remplir une liste d'item. Je lui ai fait réutiliser les fonctions 'data()' et 'setData()' pour ne manipuler que QAbstractListModel, afin d'être générique et de pouvoir remplir les rôles de n'importe lesquelles de mes classe dérivées.
    Donc pas le choix, je dois utiliser un QVariant. Je connais sont type grâce au nom du rôle associé (dans setData(role, valeur), il y a un bête switch/case), donc pas de problème. Mais je ne veux pas exposer ce variant directement à la méthode 'setJSONData'.

    Citation Envoyé par koala01 Voir le message
    Je ne peux pas terminer cette intervention sans te rappeler une règle très importante de la logique orientée objet, connue sous le nom de loi de Déméter.
    Je connaissais pas ces principes, mais intuitivement je crois que les respectes.
    Je ne voulais pas exposé un QVariant à l'intérieur de ma méthode set (bien que c'est la seule solution qui aurait marché), parce qu'en principe, je ne suis pas sensé savoir ce qu'il contient à ce niveau là.
    Et ma méthode set est l'élément de plus bas niveau pour initialisé mon mJSONData. mais petit à petit j'allais écrire des méthode de plus en plus évolué pour cacher de plus en plus de chose, à commencer par mon variant (c'est le rôle des fonctions data() et setData()). Jusqu'à arriver au final à une simple fonction "init()".
    Comme ma méthode set est une fonction très bas niveau, qui sera utilisé par plusieurs autre fonctions de niveau supérieur (init(), load(), reload(), ... j'invente, mais c'est l'esprit), je me souciais qu'elle soit bien optimisée.


    J'ai plusieurs classes dans mon projet, chacune gérant l'administration de mes différentes couches (le plus petit élément manipulable par l'utilisateur, à la souris; l'ensemble de la scène, contenant X éléments de base; mais il peut y avoir N scènes différentes dans l'appli, représentées par des onglets; et l'appli peut avoir plusieurs fenêtre;. Et enfin il y a la session complète, qui gère tout ça).
    Comme tu dis, toute la difficulté est de bien distribuer les rôles. Parfois, certains services comme tu dis, sont ambigüe, situé entre deux eaux et je passe des heures à peser le pour et le contre, si je dois l'attribuer à la classe mère ou la fille.
    Mais c'est comme ça que je vais apprendre .
    Pour l'instant, je construit toute la chaine verticale, du plus petits élément du mode A jusqu'à la session toute entière, avec restauration des paramètres de la session précédente (d'où le JSON, c'est le conteneur que j'ai choisi pour sauvegarder les données sur le disque.)
    Ca va me permettre de me débarrasser de tout ça et de bien définir les rôles des différentes couches (je touche presque à la fin).
    Après je pourrais commencer à faire le développement horizontale, en créant les autres modes de mon applis, avec leur propres scènes, leur propres items spécifique. Peut être que là, je commencerais à 'utiliser l'héritage, pour mutualiser les éléments en commun à chaque type de scène.

    Au boulot, même si c'est du C, on essai déjà d'avoir un semblant d'approche objet, par l'utilisation de typedef struct{}, de pointeurs de fonctions, de passages d'argument de pointeur void, etc...
    Mais je me rend compte aujourd'hui que c'est bien loin de ce qu'on peut faire en C++. Ici, on a pas à se soucier de l'implémentation hard de la poo (caster le pointeur void reçut en arguement en type 'ClasseDeBase', lire sont sa donnée 'type', pour connaitre sont type réel et le caster dans ce type-ci, pour pouvoir éxectuer la méthode en déréférençant sont pointeur de fonction, dont on sait à présent qu'il prend un arguement, etc, etc...).
    En c++, tout est plus facile et on peut directement se consacrer au design de l'architecture du projet et de ses classes.

    En tout cas, merci de ces conseils.


    Mais pour en revenir à la question d'origine, tu vois un moyen d'optimiser ce code (en-dehors de Qt et son implicit sharing):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    void foo(QVariant const & variant, MaClasse & cl){
        QJsonArray array = variant.toJsonArray();
        cl.setJSONData(array);
    }
    et de se passer de l'élément temporaire array et les ressources inutile qu'il utilise pour sa copie, mais tout en respectant la SRP, en évitant d'envoyer une référence de variant à setJSONData?

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

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par wistiti1234 Voir le message
    C'était bien ça mon problème. J'avais bien conscience que les données de 'array' étaient temporaires et allaient être détruite en sortie de 'foo'. Mais une fois passé dans 'setJSONData', c'est bon je les ai eut, elles sont sauvegardé en dur.
    Par contre elles auraient effectivement existé un cours instant dans 'foo', ce qui est parfaitement inutile. J'aurais aimer trouver uns solution pour que 'variant.toJsonArray' me copie directement les données dans le réceptacle finale,
    sans passer par une donnée temporaire, sans exposer "mJSONData" à foo, ni faire passer l'objet variant directement à ma méthode set.
    J'avais bien essayer ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    cl.setJSONData( &(variant.toJsonArray()) )
    mais le compilo me signalait ce problème de durée de vie limité de l'objet temporaire créé ici. Visiblement, à ce niveau là, la durée de vie était trop courte pour lui.


    Non, mJSONData est une variable en dure, pas un pointeur. Je l'avais peut-être pas précisé dans mon premier message et je me rends compte maintenant que j'avais écris un bêtise, ligne 8, c'était bien
    Non, malheureux!!!

    mJSONData est un POINTEUR. Autrement dit, ce n'est qu'une variable numérique (généralement non signée) qui permet de représenter une adresse mémoire à laquelle le système s'attend à trouver un objet du type indiqué.

    Le problème, c'est que cette adresse est celle d'une adresse temporaire, qui est détruite à la sortie de la fonction (foo dans mon exemple).

    Ce pointeur représente donc une adresse qui est devenue invalide.
    qu'il fallait lire, et pas "mJSONData = jsonData;", mais j'avais changer tellement de fois de syntaxe en tâtonnant sans plus réfléchir, qu'à la fin, en remettant le code propre, ça m'avais échappé.
    De plus, j'imaginais bien que Qt avait implémenté la fonction '=' comme il se doit, par copie bit à bit (bon en fin de compte, ils utilisent l'implicit sharing'), donc pour moi, les données sont bien en place à la fin.
    L'implicite sharing n'a strictement rien à voir avec la choucroute!

    Ici, le problème est que QJsonArray array; et QJsonArray * ptr; sont deux choses totalement différente : le premier est un objet de type QJsonArray alors que le second est un pointeur sur un QJsonArray.

    Bien sur, on peut toujours prendre l'adresse d'un objet avec l'opérateur address of (l'esperluette), mais cela n'influe en rien dans la durée de vie de la variable dont on a pris l'adresse!

    Pas le choix. J'ai hérité de la classe QAbstractListModel afin de pouvoir envoyer une liste d'objet à ma vue QML.
    Elle implémente les fonctions 'data(role)' et setData(role, valeur) , qui ne manipulent que des QVariant afin d'accèder aux attributs de mes items, représentées par un string pour le nom et une donnée pouvant être de n'importe quelle type.
    Il se trouve que dans mon cas, un des "rôles" est lui-même une QJsonArray, contenant les données d'une liste d'objets de plus bas niveau (qui repasseront eux-aussi par des sous-fonctions data() et setData()).
    au niveau du modèle, oui, en effet.

    Mais c'est là qu'intervient la distinction entre le modèle et les données métier: Typiquement, tu vas créer un modèle au départ de données métiers existantes. Dans ton cas, tu va créer un QVariant au départ d'un QJsonArray qui sera utilisé par le modèle pour discuter avec la vue.

    Mais, si tu regardes d'un peu plus près l'interface de QVariant, tu te rends compte que c'est une classe qui n'expose que des fonctions constantes, ou peu s'en faut.

    Tu ne peux donc pas partir du QVariant pour modifier le contenu de la variable sous-jacente (ton QJsonArray en l'occurrence).

    Au niveau du modèle, tu dois donc séparer les choses en deux catégories clairement différentes :
    1. la communication entre le modèle et la vue d'une part, qui s'effectue au travers d'un QVariant et
    2. la communication entre le modèle et la donnée métier (classe qui maintient réellement ton QJsonArray) de l'autre.


    Pour ce qui est de cette deuxième catégorie, tu ne peux envisager de modifier le contenu de ton QJsonArray qu'au travers de l'interface de cette classe particulière.

    Mais, la solution qui consiste à fournir un QJsonArray * afin d'appliquer cette modification n'est très clairement pas la meilleure

    Tu dois donc, au niveau du modèle, essayer de récupérer les modifications qui seront apportée (au travers des données reçue depuis ton interface graphique) et invoquer les services "qui vont bien" de la classe qui manipule effectivement ton QJsonArray
    Côté C++ j'ai écris une fonction qui parse un fichier JSON pour créé et remplir une liste d'item. Je lui ai fait réutiliser les fonctions 'data()' et 'setData()' pour ne manipuler que QAbstractListModel, afin d'être générique et de pouvoir remplir les rôles de n'importe lesquelles de mes classe dérivées.
    Donc pas le choix, je dois utiliser un QVariant. Je connais sont type grâce au nom du rôle associé (dans setData(role, valeur), il y a un bête switch/case), donc pas de problème. Mais je ne veux pas exposer ce variant directement à la méthode 'setJSONData'.
    J'hésite sur la manière d'interpréter ce que tu me dis là, mais peut être que mes explications précédantes t'auront permis de comprendre où est le problème

    Je connaissais pas ces principes, mais intuitivement je crois que les respectes.
    Je ne voulais pas exposé un QVariant à l'intérieur de ma méthode set (bien que c'est la seule solution qui aurait marché), parce qu'en principe, je ne suis pas sensé savoir ce qu'il contient à ce niveau là.
    Dés que tu utilises un mutateur (setXXX), dis toi qu'il y a très peu de chances pour que tu respecte la loi de Demeter


    Et ma méthode set est l'élément de plus bas niveau pour initialisé mon mJSONData. mais petit à petit j'allais écrire des méthode de plus en plus évolué pour cacher de plus en plus de chose, à commencer par mon variant (c'est le rôle des fonctions data() et setData()). Jusqu'à arriver au final à une simple fonction "init()".
    J'ai l'impression que tu essayes de faire en sorte que ton modèle s'occupe de la gestion des données métiers

    Or, ton modèle ne sert que de "passerelle" entre la vue et les données métier, afin d'indiquer à l'une que les autres ont changé ou aux autres qu'il faut se mettre à jour suite à une demande de la part de la vue.

    Tu dois donc avoir "une classe supplémentaire" qui ne s'occupe que de la manipulation des données métier (en gros : ton QJsonArray) de manière totalement indépendante de ton modèle, et qui n'expose que des services susceptibles de mettre à jour / récupérer des informations propres à ton QJsonArray
    Comme ma méthode set est une fonction très bas niveau, qui sera utilisé par plusieurs autre fonctions de niveau supérieur (init(), load(), reload(), ... j'invente, mais c'est l'esprit), je me souciais qu'elle soit bien optimisée.
    Sauf qu'une fonction set implique que tu sais que tu manipule un QJsonArray en interne. Or, en vertu de la loi de Déméter, tu ne devrais pas le savoir

    J'ai plusieurs classes dans mon projet, chacune gérant l'administration de mes différentes couches (le plus petit élément manipulable par l'utilisateur, à la souris; l'ensemble de la scène, contenant X éléments de base; mais il peut y avoir N scènes différentes dans l'appli, représentées par des onglets; et l'appli peut avoir plusieurs fenêtre;. Et enfin il y a la session complète, qui gère tout ça).
    Comme tu dis, toute la difficulté est de bien distribuer les rôles. Parfois, certains services comme tu dis, sont ambigüe, situé entre deux eaux et je passe des heures à peser le pour et le contre, si je dois l'attribuer à la classe mère ou la fille.
    Mais c'est comme ça que je vais apprendre .
    Pour l'instant, je construit toute la chaine verticale, du plus petits élément du mode A jusqu'à la session toute entière, avec restauration des paramètres de la session précédente (d'où le JSON, c'est le conteneur que j'ai choisi pour sauvegarder les données sur le disque.)
    Ca va me permettre de me débarrasser de tout ça et de bien définir les rôles des différentes couches (je touche presque à la fin).
    Après je pourrais commencer à faire le développement horizontale, en créant les autres modes de mon applis, avec leur propres scènes, leur propres items spécifique. Peut être que là, je commencerais à 'utiliser l'héritage, pour mutualiser les éléments en commun à chaque type de scène.
    Tu devrais surtout commencer par une classe, qui n'hérite absolument pas d'un modèle quelconque, qui permette la manipulation ton QJsonArray de manière transparente.

    Regarde au niveau de l'interface de QJsonArray pour déterminer les fonctions qui t'intéressent réellement.

    Parmi toutes les fonctions publiques de cette classe, il n'y en a sans doute qu'une partie que tu invoquera réellement au niveau de ta classe. Par exemple, cela pourrait prendre la forme 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
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    /** Représente les données métier que nous avons récupérée de manière transparente pour l'utilisateur.
      * 
      * Nous savons que nos données seront récupérées au travers d'un fichier JSon, mais 
      * l'utilisateur n'a absolument pas à s'en inquiéter.
      * 
      * Tout ce dont l'utilisateur a à savoir, c'est qu'il va rajouter un certain nombre de données 
      * de type plus ou moins primitif (int, double, bool, QString ou autres) à notre classe et 
      * qu'il sera en mesure de les récupérer sous la forme de QJsonValue (qui n'est, à peu 
      * de chose près, qu'un QVariant particulier pour le format JSon)
      */
    class MyQJsonManipulator{
        public:
            /** typedef sur l'itérateur non constant permettant de parcourir l'ensemble des 
             *données (C++11 inside)
             */
            using iterator = QJsonArray::iterator
            /** typedef sur l'itérateur constant permettant de parcourir l'ensemble des données
              * (C++11 inside)
              */
            using const_iterator = QJsonArray::const_iterator
            /* permet d'ajouter un nouvel élément de n'importe quel type primitif */
            template <typename T>
            void append(T const & t){
                array_.append(QJsonValue(t));
             }
             /** Permet de savoir si la collection de donnée est vide */
             bool isEmpty() const{return array.isEmpty();}
             /** Permet de récupérer le nombre de données que contient notre collection */
             int count() const{return array_.count();}
             /** Permet l'insertion d'une nouvelle valeur à l'index position */
             template <typename T>
             void insert(int index, T const & t){
                 array_.insert(index, QJsonValue(t));
             }
             /** Permet l'insertion d'une nouvelle valeur après l'élément indiqué */
             template <typename T>
             void insert(iterator after, T const & t){
                 array_.insert(after, QJsonValue(t));
             }
             /* Permet de récupérer un iterateur non constant sur le premier élément
              * de la collection
              */
             iterator begin(){return array_.begin();}
             /* Permet de récupérer un iterateur non constant sur ce qui représente la fin
              * de la collection
              */
             iterator end(){return array_.end();}
             /* Permet de récupérer un iterateur constant sur le premier élément
              * de la collection
              */
             const_iterator begin() const{return array_.begin();}
             /* Permet de récupérer un iterateur constant sur ce qui représente la fin
              * de la collection
              */
             const_iterator end() const{return array_.end();}
             /** Permet d'ajouter une donnée au début de la collection */
             template <typename T>
             void prepend(T const & t){
                 array_.prepend(QJsonValue(t));
             }
             /** permet de récupérer la valeur se trouvant à l'index indiqué */
            QJsonValue at(int index) const{return array_.at(index);}
            QVariant toVariant() const{return QVariant(array_);}
            /* NOTE : dans un premier temps, je considère que nous ne retirerons jamais 
             * d'élément de cette collection... Peut être pour plus tard ! ;)
             *
             */
     
        private:
            QJsonArray array;
    };
    Une fois que tu as cette classe, tu peux facilement la manipuler sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void foo(){
        MyQJsonManipulator json;
        json.append(1);
        QString str("salut");
         json.append(str);
         /* ... */
        auto it = json.begin();/* C++11 inside */
        while(it!= json.end()){
            /* ... */
            ++it;
        }
    }
    Et tu peux créer ton modèle sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    class MyModel : public QAbstractModel{ // ou n'importe quel autre type de modèle
        public:
            MyModel(MyQJsonManipulator & json):json_(json){}
            /* ... */
        private:
            MyQJsonManipulator & json_;
    };
    Quand, dans une des fonctions de ton modèle, tu as besoin de manipuler un QVariant, tu peux parfaitement travailler 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
    11
    class MyModel : public QAbstractModel{ // ou n'importe quel autre type de modèle
        public:
            MyModel(MyQJsonManipulator & json):json_(json){}
            void functionNeedingAQVariant() const{
                QVariant variant=json_.toVariant();
                /* tu manipules variant ici */
            }
            /* ... */
        private:
            MyQJsonManipulator & json_;
    };
    et, si tu veux pouvoir modifier les données, tu travailles sous la forme 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
    class MyModel : public QAbstractModel{ // ou n'importe quel autre type de modèle
        public:
            MyModel(MyQJsonManipulator & json):json_(json){}
            void functionNeedingAQVariant() const{
                QVariant variant=json_.toVariant();
                /* tu manipules variant ici */
            }
            void functionManipulatingBusinessDatas(/* ... */){
                /* tu invoques les fonctions "qui vont bien" de json_ */
            }
            /* ... */
        private:
            MyQJsonManipulator & json_;
    };
    A ce moment là, tu commencera à respecter l'esprit de l'idiome Model/View/Controller. Et tout ton développement n'en sera que simplifié

    Au boulot, même si c'est du C, on essai déjà d'avoir un semblant d'approche objet, par l'utilisation de typedef struct{}, de pointeurs de fonctions, de passages d'argument de pointeur void, etc...
    Disons que vous avez surtout une approche de l'encapsulation.

    L'encapsulation est, certes, une part très importante de la POO, mais ce n'en est certainement pas le principe de base.

    Le principe de base de la POO est la substituabilité : le fait de pouvoir manipuler un objet qui n'est connu que comme étant "du type parent" de cet objet
    Mais je me rend compte aujourd'hui que c'est bien loin de ce qu'on peut faire en C++. Ici, on a pas à se soucier de l'implémentation hard de la poo (caster le pointeur void reçut en arguement en type 'ClasseDeBase', lire sont sa donnée 'type', pour connaitre sont type réel et le caster dans ce type-ci, pour pouvoir éxectuer la méthode en déréférençant sont pointeur de fonction, dont on sait à présent qu'il prend un arguement, etc, etc...).
    En c++, tout est plus facile et on peut directement se consacrer au design de l'architecture du projet et de ses classes.
    C'est très certainement plus facile, mais cela demande beaucoup plus d'attention, même s'il faut porter son attention ailleurs

    En tout cas, merci de ces conseils.
    Mais, de rien
    Mais pour en revenir à la question d'origine, tu vois un moyen d'optimiser ce code (en-dehors de Qt et son implicit sharing):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    void foo(QVariant const & variant, MaClasse & cl){
        QJsonArray array = variant.toJsonArray();
        cl.setJSONData(array);
    }
    et de se passer de l'élément temporaire array et les ressources inutile qu'il utilise pour sa copie, mais tout en respectant la SRP, en évitant d'envoyer une référence de variant à setJSONData?
    Si tu comprends le principe du code que je viens de te donner, tu auras ta réponse (n'hésites pas à demander des éclaircissements )
    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

Discussions similaires

  1. [POO] Est-il possible de récupérer les arguments muets d'une fonction ?
    Par RomainVALERI dans le forum Général JavaScript
    Réponses: 24
    Dernier message: 16/11/2009, 13h51
  2. Tracer les requêtes exécutées par une fonction PL/PgSQL
    Par scheu dans le forum Administration
    Réponses: 1
    Dernier message: 02/02/2009, 14h01
  3. Retrouver les valeurs des paramètres des fonctions d'une DLL
    Par Bernard Martineau dans le forum Langage
    Réponses: 6
    Dernier message: 08/11/2005, 10h42
  4. [POO] passage nom objet dsn les parametres d'une fonction
    Par melou dans le forum Général JavaScript
    Réponses: 12
    Dernier message: 21/10/2005, 17h26

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