Confused by how the reference vector function rvalue :: push_back improves efficiency
From cppreference.com I found a simple example using std :: move:
std::string str = "Hello";
std::vector<std::string> v;
// uses the push_back(const T&) overload, which means
// we'll incur the cost of copying str
v.push_back(str); // First push_back
std::cout << "After copy, str is \"" << str << "\"\n";
// uses the rvalue reference push_back(T&&) overload,
// which means no strings will be copied; instead, the contents
// of str will be moved into the vector. This is less
// expensive, but also means str might now be empty.
v.push_back(std::move(str)); // Second push_back
The comment says that the string copy has been removed.
The first push_back will call: void push_back(const value_type& _Val)
The second push_back will call: void push_back(value_type&& _Val)
I checked the implementation code for two functions:
void push_back(const value_type& _Val)
{ // insert element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
size_type _Idx = _STD addressof(_Val) - _Unfancy(this->_Myfirst());
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
this->_Myfirst()[_Idx]);
++this->_Mylast();
}
else
{ // push back a non-element
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_Val);
++this->_Mylast();
}
}
and
void push_back(value_type&& _Val)
{ // insert by moving into element at end
if (_Inside(_STD addressof(_Val)))
{ // push back an element
size_type _Idx = _STD addressof(_Val) - _Unfancy(this->_Myfirst());
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(this->_Myfirst()[_Idx]));
++this->_Mylast();
}
else
{ // push back a non-element
if (this->_Mylast() == this->_Myend())
_Reserve(1);
_Orphan_range(this->_Mylast(), this->_Mylast());
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(_Val));
++this->_Mylast();
}
}
So, as per my understanding, both first push_back ( v.push_back(str);
) and second push_back ( v.push_back(std::move(str));
) will call the vector to construct the type variable std::string
and attach it to the vector.
So, in fact, in both calls to push_back, the line was not copied. And for both push_backs, the costs are the same because both calls basically do the same thing, except that the second push_back will make the input str
empty.
In terms of efficiency, the only difference I can think of is that the second push_back will not call delete [] cstring; in the destructor of std :: string, which makes the second call to push_back a little more efficient.
Not sure if my understanding is correct. Many thanks!
source to share
The difference is here:
this->_Getal().construct(_Unfancy(this->_Mylast()),
_STD forward<value_type>(_Val));
against
this->_Getal().construct(_Unfancy(this->_Mylast()),
_Val);
Now that forward<value_type>
actually calls std::move
on std::string
.
In one case, we build a std::string
on std::string const&
, and in the other, on std::string &&
.
So, to see the difference, we have to examine what these two different constructors do.
std::string
usually implemented with SBO (Small Buffer Optimization). If the string is short (a dozen characters), the string is stored in std::string
.
If it is longer, std::string
a pointer to it is stored instead .
If you have an SBO active, both move and copy copies of the bytes. Move can then clear the source.
std::string(std::string const&)
in the case of non-SBO, perform an allocation and duplicate the buffer containing the characters.
std::string(std::string &&)
in the case of non-SBO, moves the pointer to the target and empties the source. No memory allocation occurs and zero bytes are copied.
This is what the overload provides push_back(&&)
.
source to share
Except for optimizations such as "Small Line", std::basic_string
contains what is basically a pointer to a dynamically allocated area for characters in a string. This copy is eliminated because the moved object could simply steal the moved pointer rather than allocate it and copy the data.
source to share