简体   繁体   中英

I can't underestand how move semantics work to std::string

explicit Foo(std::string p_str) : m_str(std::move(p_str)) { ... }

Full source code

I've made a constructor takes argument with move semantics. ( p_str -> m_str ) For a closer look, I opened the library header file basic_string.h but there is one thing that I can't understand.

basic_string(basic_string&& __str) noexcept :
         _M_dataplus(_M_local_data(), std::move(__str._M_get_allocator())) {
     if (__str._M_is_local()) {
         traits_type::copy(_M_local_buf, __str._M_local_buf, _S_local_capacity + 1);
     } else {
         _M_data(__str._M_data());
         _M_capacity(__str._M_allocated_capacity);
     }

     _M_length(__str.length());
     __str._M_data(__str._M_local_data());  // (#)
     __str._M_set_length(0);
 }

This is move constructor of basic_string class. After (#) is executed, p_str turns to ".\\000\\000\\000\\000\\000\\000\\000..." . As I know, basic_string stores strings in the array _M_local_buf and method _M_local_data() returns its address.

Then, why __str._M_data(__str._M_local_data()); changes p_str to zero-filled string? Isn't p_str ( __str ) still having the original strings?

What you're seeing is short string optimization.

libstdc++'s std::string implementation has two places it can store string data. It stores long strings in a dynamically allocated array of char s. To avoid the overhead of allocating that array, it can also store short strings in the space it usually uses to track the size of that dynamically allocated array. That's _M_local_data .

_M_dataplus._M_p holds a pointer to the first element of whicherver buffer is currently in use. That's the pointer that _M_data() returns, and the pointer that _M_data(pointer) sets.

So putting that together, doing

__str._M_data(__str._M_local_data());
__str._M_set_length(0);

is telling the moved-from string "Forget about any buffer you were previously managing, start using your local buffer for storage, and assume you hold no data".


Then, why __str._M_data(__str._M_local_data()); changes p_str to zero-filled string?

It doesn't. It changes p_str 's data pointer to point to the memory that previously held the size of its externally-allocated array. What you're seeing are the leftovers of that capacity (Did p_str have a capacity of 46 by chance? That's the ASCII code point for '.' ). For the moment between those two lines, p_str is in an inconsistent state. Its size is still set to its previous size, but the buffer it now points to doesn't hold that number of characters. That gets rectified on the next line when the move constructor sets its length to 0 .

Your std::string stores its contents in one of two ways:

  • If the string is short, it is stored directly in _M_local_buf , which is presumably an array member of std::string (and also accessible via _M_local_data() ).
  • If the string is long, a char array is allocated separately and a pointer to it is stored in _M_p (which is also accessible via _M_data() ).

The if statement deals with the two possible representations:

  • If the contents are stored directly in the std::string object ( __str._M_is_local() ), we copy the data into our own _M_local_buf array.
  • Otherwise we simply copy the pointer (as returned by _M_data() ) and capacity.

    Right now our objects are in an inconsistent state: Both this->_M_data() and __str._M_data() point to the same allocated storage; they both "own" it. To resolve this situation, we need to reset __str so it won't try to free our string data out from under us when it is destroyed. We'll do that in a second.

_M_length(__str.length()); copies over the length value from __str .

__str._M_data(__str._M_local_data()); makes _M_p point to the internal array that is part of the __str object. If __str wasn't a short string to begin with, this array contains garbage. But that doesn't matter, because:

__str._M_set_length(0) resets the length of __str to 0. The contents of __str._M_local_buf don't matter because 0 of them are "valid", ie part of the string.

In short: This move constructor copies the member variables of __str into our new object, then makes __str an empty string. (The string contents have been "moved" from __str into *this , so now __str is empty.)

This is a general principle: Moving from a variable destroys its contents. It will be a valid object (a valid std::string in this case), but you cannot rely on any data still being there.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM