1

I'm trying to understand about std::move. In my code I'm moving an element from std::list<struct Data> where struct Data internally contains two std::string fields, but I'm not getting expected output. This is my code:

#include <iostream>
#include <string>
#include <list>

struct Data {
    std::string topic {};
    std::string msg {};

    Data(const std::string& topic, const std::string& msg) {
        this->topic = topic;
        this->msg = msg;
    }
};

int main() {
    std::list<Data> data_list;
    data_list.push_back(Data("A", std::string(1000, 'a')));
    data_list.push_back(Data("B", std::string(1000, 'b')));
    data_list.push_back(Data("C", std::string(1000, 'c')));
    data_list.push_back(Data("D", std::string(1000, 'd')));
    data_list.push_back(Data("E", std::string(1000, 'e')));
    
    while (!data_list.empty()) {
        std::cout << (void*)&data_list.front() << "\n";
        Data&& d1 = std::move(data_list.front());
        data_list.pop_front();
        std::cout << d1.topic << ", " << d1.msg << "\n";
        std::cout << (void*)&d1 << "\n\n";
    }
    std::cout << std::endl;
}
east1000
  • 1,240
  • 1
  • 10
  • 30
Harry
  • 2,177
  • 1
  • 19
  • 33
  • 8
    You store a reference to the first element and then pop the first element. You get a dangling reference and undefined behavior. – Lukas-T Sep 10 '21 at 11:56
  • You can't move this specific instance. But if you do as NathanOliver did in his good answer, the implicit move-ctor will move the memory managed by the two string members to the new instance (instead of copying the memory). Yeah, `std::move` is not really intuitive to use :) – Lukas-T Sep 10 '21 at 12:09
  • 1
    Consider using `emplace_back()`, like so: `data_list.emplace_back("A", std::string(1000, 'a'))`. – G. Sliepen Sep 11 '21 at 16:15

1 Answers1

6

The issue here is you are not actually moving anything. When you call std::move, nothing is actually moved. What it does do is converts the lvalue that you have into an rvalue, so that it can then be move constructed or move assigned from. That's not what you are doing here though. You use

Data&& d1 = std::move(data_list.front());

which has d1 as an rvalue reference, meaning no move again, it's just a reference to the object in the list. When you pop that element, your reference is now refering to an object that no longer exits, and using it has undefined behavior leading to the output that you are seeing. If you want to move the element, you need

Data d1 = std::move(data_list.front());

and now d1 will use Data's implicit move constructor to move the list element into d1.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • `Data d1 = std::move(data_list.front());` statement doesn't a create a copy of the `std::list` element? – Harry Sep 10 '21 at 12:04
  • 2
    @Harry No, it moves `data_list.front()` into `d1`. Your data already does. `std::string` is a class type. It has a move constructor as well. Since it does, the compiler generated move constructor of `Data` is also going to call the move constructor of the strings. This might clear things up for you: https://stackoverflow.com/questions/3106110/what-is-move-semantics – NathanOliver Sep 10 '21 at 12:09
  • what if struct Data in turn contains another user defined struct as field which doesn't implement neither move constructor nor move assignment operator? – Harry Sep 10 '21 at 12:17
  • @Harry Just saw your updated second comment. In that case, it falls back to a copy if it can't be moved. If it also can't be coped, then you would get a compiler error. – NathanOliver Sep 10 '21 at 12:17
  • I've one last doubt, assume that the internal struct field contains say 10000 integer fields. How to efficiently implement a move constructor or move assignment operator in that case, because I'm feeling in this struct of 10000 integer fields case there is no way to move but to copy the entire 10000 integers, even if move constructor or move assignment operator are implemented. Am I correct about this? – Harry Sep 10 '21 at 12:23
  • 1
    Yes, things like `int` don't benifit from moving, it's still a copy. Where moving gives you a boost, is when the class points to its data (like a vector). When you have that, then moving just means copy the pointer to the destination, and then set the source to null so it no longer points to the data. This lets you "move" the pointed to data from one object to another. – NathanOliver Sep 10 '21 at 12:26