Reputation: 1360
explicit Foo(std::string p_str) : m_str(std::move(p_str)) { ... }
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?
Upvotes: 0
Views: 1056
Reputation: 30619
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());
changesp_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
.
Upvotes: 2
Reputation: 85867
Your std::string
stores its contents in one of two ways:
_M_local_buf
, which is presumably an array member of std::string
(and also accessible via _M_local_data()
).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:
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", i.e. 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.
Upvotes: 1