Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Does Range-Based For on Qt Container Do a Deep Copy?
Forum Updated to NodeBB v4.3 + New Features

Does Range-Based For on Qt Container Do a Deep Copy?

Scheduled Pinned Locked Moved Solved General and Desktop
11 Posts 4 Posters 937 Views 5 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • C Offline
    C Offline
    Crag_Hack
    wrote on 21 Sept 2023, 23:55 last edited by
    #1

    Hi I scoured the web for this information but couldn't verify my suspicions... I am wondering if you do a simple ranged based for loop on a non-shared Qt container if the loop does a deep copy of the container. So like this:

    QVector<QString> awesomeVector;
    awesomeVector << "awesome string" << "awesome string 2";
    for (QString& string : awesomeVector) // does this do a deep copy?
        string = "unawesome string";
    

    I'm betting my left arm there is no deep copy... hope I'm right or I'll be living the rest of my life without an arm. Thanks!

    P C 2 Replies Last reply 22 Sept 2023, 03:51
    0
    • C Offline
      C Offline
      Crag_Hack
      wrote on 23 Sept 2023, 23:10 last edited by
      #11

      TLDR for googlers thx to Chris Kawa & JHilk:

      QVector<QString> awesomeVector;
      for (QString& string : awesomeVector) // deep copy if container is shared & reference count greater than 1, otherwise no deep copy
      
      
      const QVector<QString> awesomeVector;
      for (const QString& string : awesomeVector) // no deep copy
      
      
      QVector<QString> awesomeVector;
      for (const QString& string : qAsConst(awesomeVector)) // no deep copy
      
      1 Reply Last reply
      0
      • C Crag_Hack
        21 Sept 2023, 23:55

        Hi I scoured the web for this information but couldn't verify my suspicions... I am wondering if you do a simple ranged based for loop on a non-shared Qt container if the loop does a deep copy of the container. So like this:

        QVector<QString> awesomeVector;
        awesomeVector << "awesome string" << "awesome string 2";
        for (QString& string : awesomeVector) // does this do a deep copy?
            string = "unawesome string";
        

        I'm betting my left arm there is no deep copy... hope I'm right or I'll be living the rest of my life without an arm. Thanks!

        P Offline
        P Offline
        Pl45m4
        wrote on 22 Sept 2023, 03:51 last edited by
        #2

        @Crag_Hack

        You can read it yourself here

        • https://doc.qt.io/qt-5/qvector.html

        It's stated:

        Note that using non-const operators and functions can cause QVector to do a deep copy of the data. This is due to implicit sharing.

        QVector is, like many other Qt classes, implicity shared

        • https://doc.qt.io/qt-5/implicit-sharing.html#deep-copy

        If debugging is the process of removing software bugs, then programming must be the process of putting them in.

        ~E. W. Dijkstra

        1 Reply Last reply
        3
        • C Crag_Hack
          21 Sept 2023, 23:55

          Hi I scoured the web for this information but couldn't verify my suspicions... I am wondering if you do a simple ranged based for loop on a non-shared Qt container if the loop does a deep copy of the container. So like this:

          QVector<QString> awesomeVector;
          awesomeVector << "awesome string" << "awesome string 2";
          for (QString& string : awesomeVector) // does this do a deep copy?
              string = "unawesome string";
          

          I'm betting my left arm there is no deep copy... hope I'm right or I'll be living the rest of my life without an arm. Thanks!

          C Offline
          C Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on 22 Sept 2023, 05:51 last edited by
          #3

          @Crag_Hack said:

          I'm betting my left arm there is no deep copy

          The appropriate services will be collecting your left arm shortly :)

          QVector<QString> awesomeVector;
          for (QString& string : awesomeVector) // copy
          
          
          const QVector<QString> awesomeVector;
          for (const QString& string : awesomeVector) // no copy
          
          
          QVector<QString> awesomeVector;
          for (const QString& string : qAsConst(awesomeVector)) // no copy
          
          1 Reply Last reply
          5
          • J Online
            J Online
            J.Hilk
            Moderators
            wrote on 22 Sept 2023, 06:27 last edited by
            #4

            I'm actually on the OP's side of this, Q_FOREACH does the deep copy no matter what, the standard c++ for range loop is "usually" fine when working for references

            as a proof:

            class MyClass {
            public:
                QString str;
            
                // Default constructor
                MyClass(QString s) : str(std::move(s)) {
                    qDebug() << "Default constructor called";
                }
            
                // Copy constructor
                MyClass(const MyClass& other) : str(other.str) {
                    qDebug() << "Copy constructor called";
                }
            
                // Move constructor
                MyClass(MyClass&& other) noexcept : str(std::move(other.str)) {
                    qDebug() << "Move constructor called";
                }
            
                // Copy assignment operator
                MyClass& operator=(const MyClass& other) {
                    str = other.str;
                    qDebug() << "Copy assignment operator called";
                    return *this;
                }
            
                // Move assignment operator
                MyClass& operator=(MyClass&& other) noexcept {
                    str = std::move(other.str);
                    qDebug() << "Move assignment operator called";
                    return *this;
                }
            };
            
            #include <QApplication>
            
            int main(int argc, char *argv[])
            {
            
                QApplication app(argc,argv);
            
            QVector<MyClass> awesomeVector;
                    awesomeVector << MyClass("awesome string") << MyClass("awesome string 2");
                    qDebug() << "Loop Start";
                    for (auto& myClass : awesomeVector) // does this do a deep copy?
                        myClass.str = "unawesome string";
                    qDebug() << (awesomeVector.at(0).str) << awesomeVector.at(1).str;
            
                return app.exec();
            }
            

            results in no copy on write.

            Default constructor called
            Move constructor called
            Default constructor called
            Move constructor called
            Loop Start
            "unawesome string" "unawesome string"
            

            Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


            Q: What's that?
            A: It's blue light.
            Q: What does it do?
            A: It turns blue.

            C 1 Reply Last reply 22 Sept 2023, 06:59
            2
            • J J.Hilk
              22 Sept 2023, 06:27

              I'm actually on the OP's side of this, Q_FOREACH does the deep copy no matter what, the standard c++ for range loop is "usually" fine when working for references

              as a proof:

              class MyClass {
              public:
                  QString str;
              
                  // Default constructor
                  MyClass(QString s) : str(std::move(s)) {
                      qDebug() << "Default constructor called";
                  }
              
                  // Copy constructor
                  MyClass(const MyClass& other) : str(other.str) {
                      qDebug() << "Copy constructor called";
                  }
              
                  // Move constructor
                  MyClass(MyClass&& other) noexcept : str(std::move(other.str)) {
                      qDebug() << "Move constructor called";
                  }
              
                  // Copy assignment operator
                  MyClass& operator=(const MyClass& other) {
                      str = other.str;
                      qDebug() << "Copy assignment operator called";
                      return *this;
                  }
              
                  // Move assignment operator
                  MyClass& operator=(MyClass&& other) noexcept {
                      str = std::move(other.str);
                      qDebug() << "Move assignment operator called";
                      return *this;
                  }
              };
              
              #include <QApplication>
              
              int main(int argc, char *argv[])
              {
              
                  QApplication app(argc,argv);
              
              QVector<MyClass> awesomeVector;
                      awesomeVector << MyClass("awesome string") << MyClass("awesome string 2");
                      qDebug() << "Loop Start";
                      for (auto& myClass : awesomeVector) // does this do a deep copy?
                          myClass.str = "unawesome string";
                      qDebug() << (awesomeVector.at(0).str) << awesomeVector.at(1).str;
              
                  return app.exec();
              }
              

              results in no copy on write.

              Default constructor called
              Move constructor called
              Default constructor called
              Move constructor called
              Loop Start
              "unawesome string" "unawesome string"
              
              C Offline
              C Offline
              Chris Kawa
              Lifetime Qt Champion
              wrote on 22 Sept 2023, 06:59 last edited by Chris Kawa
              #5

              @J-Hilk Granted, this is a bit of a synthetic example, and since the vector is not actually shared at that point there is no copy.

              But the copy happens if vector is shared. It happens in the non-const begin() member of the vector. If you run it under a debugger and break in the detach() method you'll see a callstack like this:

              QList<MyClass>::detach
              QList<MyCLass>::begin  // <- this is called by the for loop
              main
              ...
              

              The detach does copy if vector is shared at that point, so yeah, to be strict:

              QVector<QString> awesomeVector;
              for (QString& string : awesomeVector) // this won't copy
              
              
              QVector<QString> awesomeVector;
              QVector<QString> sharedVector = awesomeVector; // this won't copy
              for (QString& string : awesomeVector) // but this will
              

              so it won't copy, but it will detach, and detach might copy.

              J 1 Reply Last reply 22 Sept 2023, 07:04
              4
              • C Chris Kawa
                22 Sept 2023, 06:59

                @J-Hilk Granted, this is a bit of a synthetic example, and since the vector is not actually shared at that point there is no copy.

                But the copy happens if vector is shared. It happens in the non-const begin() member of the vector. If you run it under a debugger and break in the detach() method you'll see a callstack like this:

                QList<MyClass>::detach
                QList<MyCLass>::begin  // <- this is called by the for loop
                main
                ...
                

                The detach does copy if vector is shared at that point, so yeah, to be strict:

                QVector<QString> awesomeVector;
                for (QString& string : awesomeVector) // this won't copy
                
                
                QVector<QString> awesomeVector;
                QVector<QString> sharedVector = awesomeVector; // this won't copy
                for (QString& string : awesomeVector) // but this will
                

                so it won't copy, but it will detach, and detach might copy.

                J Online
                J Online
                J.Hilk
                Moderators
                wrote on 22 Sept 2023, 07:04 last edited by
                #6

                @Chris-Kawa 👍 very true

                But I was technically correct, the best kind of correct 🤓

                alt text


                Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                Q: What's that?
                A: It's blue light.
                Q: What does it do?
                A: It turns blue.

                C 1 Reply Last reply 22 Sept 2023, 07:04
                2
                • J J.Hilk
                  22 Sept 2023, 07:04

                  @Chris-Kawa 👍 very true

                  But I was technically correct, the best kind of correct 🤓

                  alt text

                  C Offline
                  C Offline
                  Chris Kawa
                  Lifetime Qt Champion
                  wrote on 22 Sept 2023, 07:04 last edited by Chris Kawa
                  #7

                  @J-Hilk yeah yeah, OP can keep the arm I guess. We'll get it next time :P

                  EDIT: an example came to mind:

                  void func(QVector<QString> awesomeVector) // this does shallow copy/sharing
                  {
                     for (QString& string : awesomeVector) // so this detach will always copy
                  

                  so it's all about the context in which you run the for loop.

                  1 Reply Last reply
                  5
                  • C Offline
                    C Offline
                    Crag_Hack
                    wrote on 22 Sept 2023, 19:11 last edited by
                    #8

                    Excellent thanks guys. Exactly the information I was looking for.

                    1 Reply Last reply
                    0
                    • C Offline
                      C Offline
                      Crag_Hack
                      wrote on 22 Sept 2023, 19:22 last edited by Crag_Hack
                      #9

                      I think this all makes perfect sense since according to here it looks like the ranged based for loop uses iterators behind the scenes. And the non-const iterators won't trigger detach unless the container is shared. Which leads me to another question... will the following detach for a non-shared vector? Won't it just copy the string in the loop to the variable string each iteration but not actually do a deep copy of the container? Whereas if you do for (QString& string : awesomeVector) it just copies the variable string by reference.

                      QVector<QString> awesomeVector; //non-shared Qt container
                      for (QString string : awesomeVector)
                      
                      C 1 Reply Last reply 22 Sept 2023, 20:53
                      0
                      • C Crag_Hack
                        22 Sept 2023, 19:22

                        I think this all makes perfect sense since according to here it looks like the ranged based for loop uses iterators behind the scenes. And the non-const iterators won't trigger detach unless the container is shared. Which leads me to another question... will the following detach for a non-shared vector? Won't it just copy the string in the loop to the variable string each iteration but not actually do a deep copy of the container? Whereas if you do for (QString& string : awesomeVector) it just copies the variable string by reference.

                        QVector<QString> awesomeVector; //non-shared Qt container
                        for (QString string : awesomeVector)
                        
                        C Offline
                        C Offline
                        Chris Kawa
                        Lifetime Qt Champion
                        wrote on 22 Sept 2023, 20:53 last edited by
                        #10

                        @Crag_Hack It's the same for reference and value cases. A range for loop is roughly equivalent to this:

                        for (auto it = awesomeVector.begin(); it != awesomeVector.end(); ++it)
                        {
                           QString string = *it; // in case of by value loop
                           QString& string = *it; // in case of by reference loop
                           ...
                        }
                        

                        The detach of the vector happens already in the call to begin(), so whether the value is later referenced or copied doesn't change the outcome. The only factor here is whether the vector is shared at that point or not.

                        That's why you should use qAsConst (or the std::as_const equivalent) when you only want to read from the vector. It basically const casts the vector, co calling awesomeVector.begin() uses const overload of begin, which doesn't detach.

                        1 Reply Last reply
                        3
                        • C Offline
                          C Offline
                          Crag_Hack
                          wrote on 23 Sept 2023, 23:10 last edited by
                          #11

                          TLDR for googlers thx to Chris Kawa & JHilk:

                          QVector<QString> awesomeVector;
                          for (QString& string : awesomeVector) // deep copy if container is shared & reference count greater than 1, otherwise no deep copy
                          
                          
                          const QVector<QString> awesomeVector;
                          for (const QString& string : awesomeVector) // no deep copy
                          
                          
                          QVector<QString> awesomeVector;
                          for (const QString& string : qAsConst(awesomeVector)) // no deep copy
                          
                          1 Reply Last reply
                          0
                          • C Crag_Hack has marked this topic as solved on 24 Sept 2023, 03:50

                          1/11

                          21 Sept 2023, 23:55

                          • Login

                          • Login or register to search.
                          1 out of 11
                          • First post
                            1/11
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved