move semantics
-
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.
-
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: 10As 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. :)
-
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.
-
@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_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.
@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.
-
@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.