laike9m
laike9m

Reputation: 19348

Why do moving vector and moving element have different effect on vector's size?

I know size() and empty() does not require precondition thus can be called on a moved-from object and return correct result. But I don't understand the theory behind the result.

std::vector<std::string> v = {"a", "b"};
std::string x = std::move(v[0]);
std::cout << x << std::endl;          // a
std::cout << v.size() << std::endl;   // 2
std::cout << v.empty() << std::endl;  // 0, false
auto y = std::move(v);                
std::cout << v.size() << std::endl;   // 0
std::cout << v.empty();               // 1, true

As the result shows, if you move an element, the size of vector won't change. But if you move the whole vector, it becomes empty. It makes sense, but I feel like needing more explanation so that I can handle similar cases in the future.

Upvotes: 3

Views: 452

Answers (4)

laike9m
laike9m

Reputation: 19348

I want to answer this myself as I think some graph can help people(me included) understand this problem more easily.

It all comes down to what's actually "moved". Let's say we have a vector of 3 strings.

enter image description here

Now, if I move the whole vector to say another vector, then everything on the heap is owned by the moved-to vector object, nothing is left seen from the moved-from vector's perspective.

enter image description here

Obviously, size of the vector becomes 0.

If I move an element, which is a string in such case, only the string's data is gone. The data buffer of the vector is untouched.

enter image description here

Size of the vector is still 3.

Upvotes: 0

Atul
Atul

Reputation: 536

Ok i will try my best to explain and if you find any mistakes forgive me.

First of all we have to understand what is std::move(...) ?

std::move(...) takes a object and returns a rvalue reference. Thats it. Now when creating a new object we can use that rvalue reference to invoke move constructor where actual move operations happens. (cheap copy).

Lets see an example

#include <iostream>
#include <cstring>
#include <cstdlib>

using std::cout;

struct foo
{
    int *resource;

    foo() 
    : resource{new int[10]} 
    {
        cout << "c'tor called\n";
    }

    foo(const foo &arg) 
    :foo{}  // delegating constructors. calls above constructor first then execute my body. 
    {

        /* Here expensive copy happens */

        memcpy(resource, arg.resource, 10);
        cout << "copy c'tor called\n";
    }

    foo(foo &&arg) 
    : resource{arg.resource}  // just change ownership of resource. 

    {
        /* 
            Here actual move happens. 
            Its implementator's job.
        */

        cout << "move c'tor called\n";
        arg.resource = nullptr;
    }
};


int main()
{
    foo a{};
    foo b{a};               // calls copy constructor


    /*
        Do some stuff with a and b
    */

    foo c{std::move(a)}     // calls move constructor 
}

Here i create foo a object, where i initialise resource with new chunk of memory. Nothing fancy here.

In second line new object b is created by passing object a as argument. The copy constructor is invoked and as a result object b is same as object a.

Now i think of creating another object which should be same as a, and at same time i know that i wont be using object a anymore, so i do foo c{std::move(a)}.

i could have also done foo c{a}, but as i know i wont be using a anymore so swapping the contents of object is far efficient.

Inside move constructor i need to make sure to do this arg.resource = nullptr. if i wont do this, and if somebody by accident changes object a this will indirectly effect object c too.

After doing that object a is still valid and exists. Only the contents have changed. Now coming to question

std::string x = std::move(v[0]);

Ok creating new string object by calling move constructor. After this operation the v[0] still exists only the inside constents of v[0] have changed. so v.size() is 2.

auto y = std::move(v);  

after this operation new vector object y is created by calling move constructor.

Inside move constructor of vector the implementor have to do something like this arg.container_size = 0; Because the contents of vector have new owner.

Upvotes: 0

UKMonkey
UKMonkey

Reputation: 6993

When you move an object, what you are doing is saying that the new item will take full responsibility for any data or pointers contained, and that the old one will have a destructor that will not impact the new item. It is the objects responsibility to implement this promise.

In the case of when you move the object in the vector, the vector doesn't know that the object has been moved; as such the size doesn't change; but the object that the vector is holding will now be a 'blank' item that can be safely discarded - for example in the case of a unique_ptr, it will be a ptr that points to null_ptr.

Moving the vector does exactly the same things - it moves all the items within the vector to the new one - and then clears itself to ensure that on its destructor it can't impact the items it was holding.

Upvotes: 2

zoska
zoska

Reputation: 1723

std::string x = std::move(v[0]);

You are not moving element out of collection (in this case a vector). You are moving an object - an std::string instance - kept in v[0] to another object x. The collection itself is not changed.

auto y = std::move(v);

This moves an std::vector object v into y object.

Upvotes: 4

Related Questions