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!

+3


source to share


2 answers


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(&&)

.

+3


source


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.



+3


source







All Articles