Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Special Interest Groups
  3. C++ Gurus
  4. move semantics
Forum Updated to NodeBB v4.3 + New Features

move semantics

Scheduled Pinned Locked Moved Unsolved C++ Gurus
9 Posts 4 Posters 2.7k Views 3 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.
  • Kent-DorfmanK Offline
    Kent-DorfmanK Offline
    Kent-Dorfman
    wrote on last edited by
    #1

    I don't fully understand move semantics so my impression is that they are just a complicated abstraction designed to make c++ more "javaish".

    I think I get the fundemental idea but being someone who think in terms of how code looks after compilation and how it treats memory, I just don't understand.

    A guy corrected me today by stating "it's not the rvalue you are moving. It is a reference to the rvalue".

    OK...that's fine but something in the back of my brains says that is foolish.

    Ummm...a reference to a local stack variable or object becomes invalid when the current stack frame ends, so any reference to such an rvalue becomes a bug.

    Would love to see a concrete and well explained example of why/how to use move semantics...the geeks discussions I've read online don't make it any clearer to me.

    I light my way forward with the fires of all the bridges I've burned behind me.

    1 Reply Last reply
    0
    • Christian EhrlicherC Online
      Christian EhrlicherC Online
      Christian Ehrlicher
      Lifetime Qt Champion
      wrote on last edited by Christian Ehrlicher
      #2

      An example with std::vector:

      struct MoveMeIn
      {
        void moveMe(std::vector<char> &&data) {
          std::swap(data, m_data);
        }
        void copyMe(const std::vector<char> &data) {
          m_data = data;
        }
        void print() {
          std::cout << "ptr addr: " << (void*)m_data.data() << "\n";
        }
        std::vector<char> m_data;
      };
      ...
        std::vector<char> data1(10, '\0');
        std::vector<char> data2(10, '\0');
        std::cout << "data1: " << (void*)data1.data() << ", size: " << data1.size() << "\n";
        std::cout << "data2: " << (void*)data2.data() << ", size: " << data2.size() << "\n";
        MoveMeIn m;
        m.copyMe(data2);
        m.print();
        m.moveMe(std::move(data1));
        m.print();
        m.copyMe(data2);
        m.print();
        std::cout << "data1: " << (void*)data1.data() << ", size: " << data1.size() << "\n";
        std::cout << "data2: " << (void*)data2.data() << ", size: " << data2.size() << "\n";
      

      -->
      data1: 0x188c1b0, size: 10
      data2: 0x188c1d0, size: 10
      ptr addr: 0x188c600
      ptr addr: 0x188c1b0
      ptr addr: 0x188c1b0
      data1: 0x188c600, size: 10 <-- old pointer from m_data due to usage of std::swap()
      data2: 0x188c1d0, size: 10

      As you can see when data1 is moved, it's internal pointer is moved from data1 to m_data without doing a reallocation. Therefore you move the data from one object to another. No more magic. :)

      Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
      Visit the Qt Academy at https://academy.qt.io/catalog

      1 Reply Last reply
      6
      • jeremy_kJ Offline
        jeremy_kJ Offline
        jeremy_k
        wrote on last edited by
        #3

        There's another aspect to move semantics that's unrelated to copy efficiency. Consider an object that represent an identity. Copying the object to create two instances might not make sense. Moving allows the instance to escape its original scope.

        My initial thought was to use QObject as the identity object example. That doesn't work as cleanly as I would like because QObject doesn't have a move constructor. std::thread does, which might event be better.

        Imagine:

        std::thread go()
        {
            std::thread threadOne([](){ while true printf("I'm alive\n"); };
            return threadOne;
        }
        
        void f()
        {
            std:thread threadTwo = go();
        }
        

        Does this code create two threads that spend all of their time proclaiming "I'm alive", or one thread that is created in go() but continues to exist in f()? std::thread's move constructor answers that question, reinforced by the lack of a copy constructor.

        Asking a question about code? http://eel.is/iso-c++/testcase/

        1 Reply Last reply
        0
        • Christian EhrlicherC Online
          Christian EhrlicherC Online
          Christian Ehrlicher
          Lifetime Qt Champion
          wrote on last edited by
          #4

          @jeremy_k said in move semantics:

          That doesn't work as cleanly as I would like because QObject doesn't have a move constructor. std::thread does, which might event be better.

          Another usecase is a std::unique_ptr.

          Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
          Visit the Qt Academy at https://academy.qt.io/catalog

          jeremy_kJ 1 Reply Last reply
          1
          • Christian EhrlicherC Christian Ehrlicher

            @jeremy_k said in move semantics:

            That doesn't work as cleanly as I would like because QObject doesn't have a move constructor. std::thread does, which might event be better.

            Another usecase is a std::unique_ptr.

            jeremy_kJ Offline
            jeremy_kJ Offline
            jeremy_k
            wrote on last edited by
            #5

            @Christian-Ehrlicher said in move semantics:

            @jeremy_k said in move semantics:

            That doesn't work as cleanly as I would like because QObject doesn't have a move constructor. std::thread does, which might event be better.

            Another usecase is a std::unique_ptr.

            Going back to the OP, std::unique_ptr may be the most universal and java-ish example.

            Asking a question about code? http://eel.is/iso-c++/testcase/

            1 Reply Last reply
            1
            • Kent-DorfmanK Offline
              Kent-DorfmanK Offline
              Kent-Dorfman
              wrote on last edited by
              #6

              @Christian-Ehrlicher Appreciate the example. I understand the code you posted, but move semantics still kind of rub me raw. Maybe someday I'll drink the coolaid, but for now I'm comfortable with pointers and passing objects by reference.

              I light my way forward with the fires of all the bridges I've burned behind me.

              1 Reply Last reply
              0
              • M Offline
                M Offline
                mr_broccoli
                wrote on last edited by mr_broccoli
                #7

                @Kent-Dorfman The difference between move semantics and shared pointer is indeed subtle. But there is a difference none the less.

                In move sementics, you are simply rewiring the pointers of the rvalue. (Hence you pass a reference to an rvalue) that way, you have moved only adress data and not actual data. You then invalidate the original owner of that data (set to nullptr, set values to 0, whatever).

                If, for example, you have a stringliteral, that literal is stored in constant memory during the loading of your program. Meaning that that adress remains valid even though it is considered an rvalue, thus popping the stackframe does not cause a bug in this case. This helps in reducing the amount of copies that need to happen to pass the string around. Since C++ is RAII supported, copying before initialisation is required to satifsy RAII. In come move semantics to save the day.

                A shared pointer does have similar functionality, however, the lifetime of the object remains rather unknown. Shared pointers don't delegate responsibility of cleanup, they are the "responsibility". They do this by reference counting. This is what the Java garbage collector do btw. So, more Java-ish than that is not really possible.

                Now the difference here is 2 sided.

                First, you dont use shared pointers on rvalues (usually). RValues are temporary (usualy constant) values that get destroyed (dont read fall out of scope) as soon as the lvalue is set. (This is not 100% true, but is good enough for this explaination)
                QString s = "Hello". "Hello" is the rvalue here, and that data is not "accessable" after s is set. It is only accessably since it copied the value of "Hello" in the stringbuffer (stack or otherwise) of s. Now, since "Hello" is stored somewhere in constant memory (which lives for the entire duration of the program) you have created many unnessecary copies if you have multiple object that require the same string. By moving it instead, the pointer to that adress will be the stored in s that holds the stringbuffer. Imagine the overhead if that string was large (or any datastructure for that matter).

                The second reason is that moving an object means delegating ownership to someone else. This is a very usefull idea as it keeps the original object (again without copying it) whilest invalidating other previous owners, meaning they cant delete it anymore. This gives really finetuned control over the lifetime of the object. If for example the object you used, requires you to pass it to some other class, the other class becomes responsible for that data, and using move, makes it much more clear who has ownership whilest reading code.

                In short:
                Move semantics requires less copies and has less overhead.
                Move semantics make it clear who own the object now.
                Shared pointers do reference counting, so it is harder to know when an object will be deleted (also can have cyclic reference -> memory leak none the less)
                Shared pointers are the most Java-ish solution, but it has memory and performance overhead.

                I suggest you do learn about them using pure C++, then make a judgement based on the QT framework if it is required.
                You should consider at least the QObjectTree stuff, which manages lifetimes for you by seeing what children a deleted item has, and deleting those too. And, you should consider if a unique pointer is better.

                If you want some visual support too: https://youtu.be/ehMg6zvXuMY?si=iZ2NxCokIiLWCZCD

                1 Reply Last reply
                1
                • Kent-DorfmanK Offline
                  Kent-DorfmanK Offline
                  Kent-Dorfman
                  wrote on last edited by
                  #8

                  @mr_broccoli I made peace with move semantics years ago and do use them where appropriate, along with the other nice newer feature: RVO/NRVO. After all, my post was four years ago.

                  I light my way forward with the fires of all the bridges I've burned behind me.

                  1 Reply Last reply
                  1
                  • M Offline
                    M Offline
                    mr_broccoli
                    wrote on last edited by
                    #9

                    I know haha It's more for those who come after, right!

                    1 Reply Last reply
                    0

                    • Login

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