3

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.

laike9m
  • 18,344
  • 20
  • 107
  • 140
  • Hmm, this is UB. – Vivick Jun 15 '18 at 12:34
  • This doesn't address the question, but do you really need the extra stuff that `std::endl` does? `'\n'` ends a line. – Pete Becker Jun 15 '18 at 12:35
  • @Vivick Sure it's defined - std::move doesn't delete any memory. – UKMonkey Jun 15 '18 at 12:36
  • Not really undefined, but unspecified. Cf. [What can I do with a moved-from object](https://stackoverflow.com/questions/7027523/what-can-i-do-with-a-moved-from-object) – Vivick Jun 15 '18 at 12:39
  • 3
    If you move an entire house, you’re left with an empty plot of land, but if someone moves out of an apartment in that house, the apartment is still there. – molbdnilo Jun 15 '18 at 12:44
  • 1
    @Vivick: you have extra guaranty for some classes as `std::vector`.From [doc](http://en.cppreference.com/w/cpp/container/vector/vector) *"After the move, other is guaranteed to be empty()."* – Jarod42 Jun 15 '18 at 12:44
  • @molbdnilo, so just like moving the entire house which is more expensive, would moving the entire vector more costly than just moving the element? – Joseph D. Jun 15 '18 at 12:58
  • 1
    @codekaizer the method of moving house though is to literally move the entire house; which can be faster than going through every room, and moving each item in it to the new identical house just down the road. In this analogy though - it's less like moving house and more like just changing your road name... everything stays exactly where it is, but it's just called something different – UKMonkey Jun 15 '18 at 13:04
  • @Jarod42 I cannot find this guarantee. Am I looking in the wrong section? http://eel.is/c++draft/container.requirements#general-4 (see line `a = rv`) – YSC Jun 15 '18 at 13:25
  • @YSC: [vector's doc](http://en.cppreference.com/w/cpp/container/vector/vector) claims it. I don't know from the corresponding link in standard (or if they did mistake). – Jarod42 Jun 15 '18 at 14:02
  • @Jarod42 There nothing special about vector's move constructor: http://eel.is/c++draft/vector.cons. cppreference might be in the wrong. – YSC Jun 15 '18 at 14:14

4 Answers4

4
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.

zoska
  • 1,684
  • 11
  • 23
  • what happens to `v[0]` in `v` when moved? – Joseph D. Jun 15 '18 at 12:42
  • 1
    from `v` point of view - nothing. container is not aware of operations done on its items. – zoska Jun 15 '18 at 12:47
  • 2
    @codekaizer Which move do you mean? When the vector is moved, the contained `std::string` instances are unaffected. The memory they are in will be owned by a different vector, but that is irrelevant to the string object. When you `move` the actual entry `v[0]` somewhere else, it will do the usual "take indeterminate value" thing. – Max Langhof Jun 15 '18 at 12:52
2

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.

UKMonkey
  • 6,941
  • 3
  • 21
  • 30
  • what happens to `v[0]` in `v` when moved? – Joseph D. Jun 15 '18 at 12:42
  • 1
    `v[0]` doesn't mean the same thing _before_ and _after_ `v` is moved: don't forget that `v[0]` is actually a function call hidden in sugar. – YSC Jun 15 '18 at 12:51
  • @codekaizer when v is moved, v becomes empty - which allows it to guarantee that its destructor will not impact whatever it was moved to. Which means v[0] is undefined behaviour since you are accessing an out of range index ... so who knows, maybe it'll return the old value if you're unlucky – UKMonkey Jun 15 '18 at 13:01
  • @UKMonkey, is UB same as *unspecified but valid state*? – Joseph D. Jun 15 '18 at 13:04
  • @codekaizer no. UB means your program can do pretty much anything. It could crash, it could format your hard disk, it could open a black hole ... or, perhaps the most annoying, it could work perfectly normally for the next 5 minutes and then break on what you thought would be completely unrelated. – UKMonkey Jun 15 '18 at 13:07
  • 1
    @codekaizer related: https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior – UKMonkey Jun 15 '18 at 13:08
  • the confusion is because of this [answer](https://stackoverflow.com/a/7027695/8012206), which states "*Moved-from objects exist in an unspecified, but valid, state.*" - didn't mention UB. – Joseph D. Jun 15 '18 at 13:10
  • 2
    @codekaizer it is `v` that you moved; and it's still valid. the object that `v[0]` refers to has not been moved (or maybe it has - who knows) but while `v` is valid, it is empty (this is actually specified in the standard somewhere) - leading to `v[0]` accessing an item that doesn't exist. – UKMonkey Jun 15 '18 at 13:16
  • 1
    @UKMonkey [`[container.requirements/4]`](http://eel.is/c++draft/container.requirements#general-4) is not that clear. What it says is that all elements of a moved vector are moved or destroyed. – YSC Jun 15 '18 at 13:21
  • @YSC I was paraphrasing Jarod in the comments above :) I wonder if in this case the cppreference got it wrong - maybe different cpp versions? – UKMonkey Jun 15 '18 at 13:24
0

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.

Atul
  • 546
  • 4
  • 16
0

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.

laike9m
  • 18,344
  • 20
  • 107
  • 140