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 936 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 Crag_Hack

    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!

    Pl45m4P Offline
    Pl45m4P Offline
    Pl45m4
    wrote on 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

      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!

      Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on 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.HilkJ Offline
        J.HilkJ Offline
        J.Hilk
        Moderators
        wrote on 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.

        Chris KawaC 1 Reply Last reply
        2
        • J.HilkJ J.Hilk

          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"
          
          Chris KawaC Offline
          Chris KawaC Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on 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.HilkJ 1 Reply Last reply
          4
          • Chris KawaC Chris Kawa

            @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.HilkJ Offline
            J.HilkJ Offline
            J.Hilk
            Moderators
            wrote on 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.

            Chris KawaC 1 Reply Last reply
            2
            • J.HilkJ J.Hilk

              @Chris-Kawa 👍 very true

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

              alt text

              Chris KawaC Offline
              Chris KawaC Offline
              Chris Kawa
              Lifetime Qt Champion
              wrote on 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 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 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)
                  
                  Chris KawaC 1 Reply Last reply
                  0
                  • C Crag_Hack

                    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)
                    
                    Chris KawaC Offline
                    Chris KawaC Offline
                    Chris Kawa
                    Lifetime Qt Champion
                    wrote on 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 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

                      • Login

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