Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the C++11 move constructor copy by value?

Tags:

c++

c++11

I thought the following example would have the same result as std::map::emplace(), but it didn't:

struct MoveTest {
    MoveTest():
        var(true)
    {
        cout << "ctor: " << static_cast<void*>(&var) << endl;
    }

    MoveTest(MoveTest&& mt):
        var(std::move(mt.var))
    {
        cout << "move ctor: " << static_cast<void*>(&var) << endl;
    }

    bool var;
};


int main() {
    map<int, MoveTest> mtest;
    mtest.insert(make_pair(1, MoveTest()));
    return 0;
}

output:

ctor: 0x7fff12e4e19b
move ctor: 0x7fff12e4e194
move ctor: 0x25f3034

The MoveTest::var has a different address in each move. It doesn't look like a "move". What's wrong in my code or understanding?

like image 674
GuLearn Avatar asked May 12 '26 00:05

GuLearn


2 Answers

For MoveTest as you've defined it, there's not really much a move ctor (or assignment operator) can optimize (or do in general--all it can really do is copy from source go destination).

Moving is mostly a big win when an object contains a pointer to a bunch of external memory that's (for example) allocated on the heap. In this case, the move ctor/assignment operator can basically do a shallow copy (i.e., just grab the pointer from the moved-from object) instead of a deep copy (copying all the data referred to by that pointer).

For example:

class move_tst {
    int *buffer;
    static const int size = 1024 * 1024;
public:
    move_tst() {        
        buffer = new int[size];
        std::iota(buffer, buffer + size, 0);
    }

    move_tst(move_tst const &other) {
        buffer = new int[size];
        std::copy_n(other.buffer, size, buffer);
    }

#ifdef MOVE
    move_tst(move_tst &&other) {
        buffer = other.buffer;
        other.buffer = nullptr;
    }
#endif

    ~move_tst() { 
        delete [] buffer;
    }
};

Caveat: I've used a raw invocation of new and a raw pointer here so nothing else gets involved and we wouldn't (for example) get move semantics courtesy of a smart pointer. For normal code under normal circumstances, you should not be using either (raw pointer or raw invocation of new, I mean).

Edit: as far as what std::move does, it doesn't actually do a move itself--it just signals that you no longer care about a value, so it's eligible to be the source of a move, even if that destroys its value.

For example, with the class above, you could do a test like:

for (int i = 0; i < 1000; i++) {
    move_tst src;
    move_tst dst =src;
}       

...and compare it to:

for (int i = 0; i < 1000; i++) {
    move_tst src;
    move_tst dst = std::move(src);
}       

Without the std::move, dst will be created as a copy of src, but with the std::move, it'll be able to do a move from src to dst.

like image 172
Jerry Coffin Avatar answered May 14 '26 14:05

Jerry Coffin


Your log simply tells that you create a temporary MoveTest instance with the default constructor

MoveTest()

which is then copied elsewhere, inside a pair object

make_pair(1, MoveTest())

and copied again somewhere inside the map object

mtest.insert(make_pair(1, MoveTest()));

There are three constructor invocations for three distinct MoveTest instances, each with its own address. As already well explained, whether the constructors are move constructors or old style copy constructors doesn't matter.

Did you expect some of the copies to be optimized away? With nontrivial code in the constructors it becomes unlikely; in particular, reading &var is a good reason to avoid shortcuts.

like image 30
Lorenzo Gatti Avatar answered May 14 '26 15:05

Lorenzo Gatti



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!