Partage
  • Partager sur Facebook
  • Partager sur Twitter

C++11 range loop sur les containers QT

warning : clazy-range-loop

    24 septembre 2021 à 12:10:07

    Bonjour, je travail avec Qt 5.15.2 avec le compilateur MSVC2019 64bit

    Dans mon application, j'ai une QList de pointeur sur un widget custom (StepWidget) et un layout vertical dans mon ui pour afficher ces widgets 

    J'ai une fonction clearStepWidgets qui me permet de retirer tous les widgets du layout et de supprimer les pointeurs

    voici le code :

    // dans viewmodel.h
     
    class StepWidget;
        
    class ViewModel : public QWidget
    {
    Q_OBJECT
    private:   
        QList<StepWidget*> stepWidgets {};
     
        void clearStepWidgets();
    }
     
    // dans viewmodel.cpp
    void ViewModel::clearStepWidgets()
    {
        for(auto* ptr : stepWidgets) {
            ui->stepWidgetLayout->removeWidget(ptr);
            delete ptr;
        }
        stepWidgets.clear();
    



    J'ai alors un warning du compilateur sur la ligne du for. Le message exact est le suivant :

    c++11 range-loop might detach Qt container (QList) [clazy-range-loop]

    J'ai bien évidemment recherché sur internet ce que celà voulait dire mais je vous avoue que je ne comprend pas et j'ai besoin de votre support pour m'expliquer svp.

    J'ai juste vu que cela avait quelque chose à voir avec le système de mémoire partagée des containers de Qt (mais je ne maitrise pas ce concept) et le fait que l'itérateur qui se cache derrière cette boucle n'était pas const mais ça ne m'éclaire pas beaucoup

    Je n'ai pas non plus réussis à comprendre ce que le terme 'detach' voulait dire, de quelle catastrophe ce warning essaye-t-il de me prévenir ?

    Et surtout quelle est la bonne façon de boucler sur un container de Qt ?

    Merci d'avance pour vos réponses


    -
    Edité par ThibaultVnt 24 septembre 2021 à 12:15:18

    • Partager sur Facebook
    • Partager sur Twitter
      24 septembre 2021 à 13:10:06

      Salut,

      Sauf erreur de ma part, vu que tu parle de "mémoire partagée", j'aurais tendance à penser qu'il s'agit d'un problème de gestion des thread.

      Pour faire simple, un même jeu de données (le contenu d'un QList ou autre) risquent d'être utilisées par différent thread et, forcément, si tu te mets à changer -- ou pire, à supprimer -- ces données, cela peut avoir des résultats "assez imprévisibles" sur le comportement des autres threads.

      Dans cette optique de thread, le terme "detach" est généralement utilisé pour signifier que le thread s'arrête de travailler et qu'il signale à son "thread parent" qu'il ne doit plus "faire attention à lui".

      Ceci étant dit, le message que tu reçoit est un avertissement.  C'est à dire que le compilateur a constaté que ton code pouvait éventuellement occasionner des problèmes dans certaines circonstances bien particulières, et qu'il te met donc en garde sur ce point.

      Cela ne veut absolument pas dire que le problème surviendra ** d'office **.  Cela veut "juste" dire que le compilateur n'a aucun moyen de s'assurer que le problème ne surviendra pas :p

      Il est donc de ton ressort de vérifier si le problème risque effectivement de survenir ou non, et de prendre les "mesures qui conviennent" pour éviter que cela n'arrive.

      La question est alors "es tu  en mesure de garantir que le problème ne surviendra pas?"  Si oui, tu peux ne pas tenir compte de l'avertissement ("c'est dangereux, mais le risque est bien calculé" ;) )  Si non, il faudra sans doute envisager les choses d'une manière différente, ou, peut être, simplement mettre les "gardes fous" qui éviteront que d'autres threads n'essayent d'accéder au contenu de ta QList pendant le processus de "vidange" ;).

      • Partager sur Facebook
      • Partager sur Twitter
      Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
        24 septembre 2021 à 13:52:35

        Bonjour Koala, merci de ta réponse 

        Je n'utilise pas de multithreading donc je ne pense pas qu'il faille chercher dans cette direction.

        J'ai tapé dans google "qt container detach" et le premier liens était la documentation Qt sur "implicit sharing" : https://doc.qt.io/qt-5/implicit-sharing.html mais comme j'ai dit ce n'est pas un concept que je maitrise et je n'ai que peu compris ce que j'ai lu je ne suis donc pas sûr que cet article corresponde bien à une description du problème que je rencontre dans mon code

        En tout cas ma QList de StepWidget est un membre privé de ma classe ViewModel, cette liste n'est manipulée par aucune autre classe que ViewModel et toujours dans le thread principale

        J'ai même reproduit ce warning avec un code ultra minimal. Création d'un nouveau projet sans interface graphique, juste un programme console que voici :

        #include <QCoreApplication>
        
        #include <QList>
        #include <QDebug>
        
        int main(int argc, char *argv[])
        {
            QCoreApplication a(argc, argv);
        
            QList<int> demo {};
            demo << 1 << 2 << 3 << 4;
        
            for(const auto& val : demo) {
                qDebug() << "val : " << val;
            }
        
            return a.exec();
        }

        Et j'ai aussi exactement le même warning sur la ligne du for. On dirait que Qt n'aime tout simplement pas les for range loop pour une obscure raison

        Je viens aussi d'installer le compilateur MinGW, j'ai changé de kit et le warning persiste 

        Je suis d'accord avec toi sur le fait qu'un warning n'est pas forcément une catastrophe et qu'on peut l'ignorer si on s'assure que le problème potentiel que pointe le warning n'arrivera pas. Mais comme je ne comprend pas le warning, comme je ne comprend pas quel est le problème que je vais potentiellement avoir, je suis incapable de dire si il y a un risque que ce problème me tombe dessus et je ne sais pas quoi faire pour m'en protéger 

        EDIT : Je n'ai pas précisé, mais j'ai récemment désinstallé Qt puis réinstallé ensuite. Je sais plus exactement ce que j'avais comme ancienne installation mais j'ai surement installé une version un peu différente cette fois ci parce que c'est clairement pas la première fois que je fais des fo range loop sur mes containers et pourtant c'est la première fois que je vois ce warning pop

        -
        Edité par ThibaultVnt 24 septembre 2021 à 14:02:07

        • Partager sur Facebook
        • Partager sur Twitter
          24 septembre 2021 à 14:43:31

          Tu n'as pas sciemment recours au multithreading, nuance...

          Ce que je veux dire par là, c'est que tu as -- visiblement -- abordé ton application d'un point de vue "purement séquentiel": on commence par faire ceci, puis on fait cela et on termine par autre chose.

          Seulement, le simple fait que ton interface graphique puisse continuer à réagir (à se mettre à jour, à réagir aux cliques et autres joyeusetés de la sorte, comme le simple fait de pouvoir bouger ta souris) alors que l'application est de toute évidence occupée à "faire autre chose" démontre clairement qu'il y a effectivement une gestion multithread "en arrière plan" des tâches effectuées.

          Car, si ce n'était pas le cas, tu ne pourrais même plus faire bouger ta souris pendant que ton application fait "autre chose".

          Je ne te reproche pas le fait de n'avoir pas conscience de travailler, de fait, dans un environnement multithread, vu que se mode de fonctionnement est strictement interne à l'utilisation de Qt.  Cependant, le fait de n'avoir pas envisager le multithread ne signifie pas ... qu'il n'y en ait pas.

          Et c'est -- justement -- là le but de l'avertissement émis par le compilateur.  Car lui, il a une vision beaucoup plus "globale" des situations spécifiques sur lesquels il travaille. Et le compilateur a donc pu constater que le code que tu as écrit -- en toute bonne fois, nous sommes d'accord -- dans une optique "unithread" s'applique en réalité dans un contexte "multithread", ce qui risque (on parle bien d'un risque, peut-être surévalué ... ou non) de provoquer une "désynchronisation" des données manipulées.

          Je ne connais pas ton projet.  Il se peut que cette "désynchronisation des données" contre laquelle le compilateur te met en garde n'ait ... absolument aucune chance de survenir.  Mais il se peut aussi qu'elle ait de grandes chances de survenir ... sans même que tu en aies conscience.

          Pour être tout à fait honnête, ta réponse

          ThibaultVnt a écrit:

          Je n'utilise pas de multithreading donc je ne pense pas qu'il faille chercher dans cette direction.

          me fait sincèrement craindre que tu n'aies simplement pas la compétence -- ou peut être simplement la maitrise du projet-- requise pour juger du problème:-°

          • Partager sur Facebook
          • Partager sur Twitter
          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
            24 septembre 2021 à 19:07:53

            Ce n'est pas un problème de thread, mais de "constness" 

            C'est décrit sur cppreference.com

            When used with a (non-const) object that has copy-on-write semantics, the range-based for loop may trigger a deep copy by (implicitly) calling the non-const begin() member function.

            Quand tu cliques sur l'ampoule de l'avertissement dans QtCreator, ça remplace "demo" par "qAsConst(demo)", pour forcer l'appel aux versions const de QList::begin et QList::end et éviter de faire une copie du conteneur sans raison.

            • Partager sur Facebook
            • Partager sur Twitter
              24 septembre 2021 à 19:23:14

              Pas tout lu en detail, mais alexisdm a raison, ce n'est pas lie aux threads.

              Dans les explications que tu as lu, le "paratage de la memoire" est le "implicit sharing" (ou copy-on-write ou COW), c'est a dire un partage des donnees quand tu as plusieurs copies d'un meme objet de qt a semantique de valeur (par exemple QString, QVector, QList, etc). https://doc.qt.io/qt-5/implicit-sharing.html

              L'idee est que quand tu copies un tel objet, tant que tu ne modifies pas cet objet, tu n'as pas besoin de copier les donnees internes, puisqu'elles seront les memes. Donc ces donnees sont partagees. C'est uniquement lorsque tu modifies ces donnees internes qu'elles sont copiees. C'est le sens de "copy on write" (copie lors de l'ecriture).

              L'implementation est relativement simple : les fonctions constantes des classes COW ne font pas de copie des donnees internes, les fonctions non const font des copies des donnees internes.

              Le probleme des range for loop, c'est qu'elles utilisent par defaut les version non const de begin/end. Donc cela provoque la copie, meme lorsque ce n'est pas necessaire. C'est ce qui signifie ton warning "c++11 range-loop might detach Qt container" (detach = faire la copie).

              La solution, comme indiquee par alexisdm, est d'utiliser "qAsConst" (ou "std::as_const") pour faire un cast de l'object en une constante et donc forcer le for a utiliser la version const de begin/end. Et donc ne plus faire de copie.

              • Partager sur Facebook
              • Partager sur Twitter

              Rejoignez le discord NaN pour discuter programmation.

              C++11 range loop sur les containers QT

              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
              • Editeur
              • Markdown