walkerlala
walkerlala

Reputation: 1700

mechanism that make a string empty after std::move() it

I have some confusion about how a std::move() really empty something.

I write some code:

int main()
{
    string str1("this is a string");
    std::cout<<std::boolalpha<<str1.empty()<<std::endl;
    string str2(std::move(str1));
    cout<<"str1: "<<str1.empty()<<endl;
    cout<<"str2: "<<str2.empty()<<endl;
}

The output is:

false

true //this mean the original string is emptied

false

Why is the original string emptied every time? I have read some meterial about the move semantics,including the original proposal of it(this one), which said:

The difference between a copy and a move is that a copy leaves the source unchanged. A move on the other hand leaves the source in a state defined differently for each type. The state of the source may be unchanged, or it may be radically different. The only requirement is that the object remain in a self consistent state (all internal invariants are still intact). From a client code point of view, choosing move instead of copy means that you don't care what happens to the state of the source.

So,according to this words, the original content of the str1 above should be some kind of undefined. But why is that every time it have been move(), it is emptied? (Actually I have test this behavior on both std::string and std::vector but the result is the same.)

To learn more, I define my own string class to test, as bellow:

class mstring
{
private:
    char *arr;
    unsigned size;
public:
    mstring():arr(nullptr),size(0){}
    mstring(char *init):size(50)
    {
        arr = new char[size]();
        strncpy(arr,init,size);
        while(arr[size-1] != '\0') //simply copy 
        {
            char *tmp = arr;
            arr = new char[size+=50]();
            strncpy(arr,tmp,50);
            delete tmp;
            strncpy(arr-50,init+(size-50),50);
        }
    }

    bool empty(){ return size==0;}

}

Doing the same thing:

int main()
{
    mstring str("a new string");
    std::cout<<std::boolalpha<<str.empty()<<std::endl;
    mstring anotherStr(std::move(str));
    std::cout<<"Original: "<<str.empty()<<std::endl;
    std::cout<<"Another: "<<anotherStr.empty()<<std::endl;
}

The output is:

false

Original: flase //mean that the original string is still there

Another: false

Even I add a move constructor like this:

    mstring(mstring&& rvalRef)
    {
        *this = rvalRef;
    }

The result is still the same. My question is: why is the std::string get emptied but my self-defined isn't?

Upvotes: 2

Views: 1967

Answers (2)

Jan Hudec
Jan Hudec

Reputation: 76346

So,according to this words, the original content of the str1 above should be some kind of undefined.

There is absolutely nothing about what the state should be. The specification says what the state could be: anything defined enough that it can be destroyed or assigned new value.

Empty qualifies as anything, so the state can be empty.

It also makes most sense in this case. A string is, essentially, something like

class string {
    char *_M_data;
    size_t _M_size;
    size_t _M_alloc;
public:
    ...
}

where the _M_data is allocated with new and must be deleted with delete (this is customizable with the allocator parameter, but the default allocator does just that).

Now if you don't care about the state of the source, the fastest thing that can be done is to assign the buffer to the destination and replace the buffer with nullptr in the source (so it does not get deleted twice). A string with no buffer is empty.

Upvotes: 0

Wyzard
Wyzard

Reputation: 34573

Because that's how the std::string move constructor is implemented. It takes ownership of the old string's contents (i.e. the dynamically-allocated char array), leaving the old string with nothing.

Your mstring class, on the other hand, doesn't actually implement move semantics. It has a move constructor, but all it does is copy the string using operator=. A better implementation would be:

mstring(mstring&& rvalRef): arr(rvalRef.arr), size(rvalRef.size)
{
  rvalRef.arr = nullptr;
  rvalRef.size = 0;
}

This transfers the contents to the new string and leaves the old one in the same state that the default constructor would have created it. This avoids the need to allocate another array and copy the old one into it; instead, the existing array just gets a new owner.

Upvotes: 5

Related Questions