Steven Lu
Steven Lu

Reputation: 43427

Moving a string into a vector of strings from a stringstream

I have a std::stringstream ss and a std::vector<string> list.

I want to push_back (or emplace_back) ss onto list.

How do I do this in a way that best avoids making extra copies of ss's backing string?

My intention is to immediately run ss.clear() as I'll be re-filling it anew (to append to list at a later time...)

Possible options:

  1. list.emplace_back(std::move(ss.str())
  2. list.emplace_back(ss.str())
  3. list.push_back(std::move(ss.str())
  4. list.push_back(ss.str())

What happens in each case? What will remain in ss in each case? (I'll be throwing it away in any case)

Contributing to my uncertainty about what to do is this topic. The thing is, though, I'm not moving stringstreams into one another, I'm specifically only worried at this point in time about being able to move the string built as a stringstream (which might be huge) into the back of a container of strings. Obviously if str() is implemented in such a way that causes some copy or conversion, that's just unavoidable, but hopefully I would like to generate code that will be able to just stuff it into the back of my vector in constant time.

I looked at the implementation of str():

template <class _CharT, class _Traits, class _Allocator>
basic_string<_CharT, _Traits, _Allocator>
basic_stringbuf<_CharT, _Traits, _Allocator>::str() const
{
    if (__mode_ & ios_base::out)
    {
        if (__hm_ < this->pptr())
            __hm_ = this->pptr();
        return string_type(this->pbase(), __hm_, __str_.get_allocator());
    }
    else if (__mode_ & ios_base::in)
        return string_type(this->eback(), this->egptr(), __str_.get_allocator());
    return string_type(__str_.get_allocator());
} 

and was further confused.

Upvotes: 3

Views: 2564

Answers (3)

jrok
jrok

Reputation: 55395

As already said, all four options eventualy do the same thing and are optimal - they move the temporary string returned by str() (it's an rvalue, so && overloads of push_back apply) to the back of the vector. emplace_back is beneficial when you have a set of arguments that you want to pass to the constructor of value_type. Consider:

std::vector<std::pair<std::string, std::string>> vec;

vec.push_back(std::make_pair("Hey", "You")); // a temporary pair is created here

vec.emplace_back("Hey", "You");  // arguments are forwarded to the constructor of
                                 // pair which is constructed in place

As for your case, the best you can do is to make ss an rvalue and call str() on that. Hopefully, the implementation of stringstream will be smart enough and realize it can just give out its internal buffer instead of making a copy of it:

list.emplace_back(std::move(ss).str());

Upvotes: 0

Jules Gagnon-Marchand
Jules Gagnon-Marchand

Reputation: 3781

By copy function return values are already std::moved by default. ie

std::string herp() { return string("lalalalala"); }

std::string derp (herp ());

Will use derp's std::string move constructor.

As you can see in the following code, the move assignment operator will be used. Internally, the string's own operator=(&&) will swap with the string ss.str() will return

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

class Herp {
    string internal;
    public:
        const Herp& operator=(const string& s)
        {
            cout<<"Standard initialize operator\n";
            internal = s;
            return *this;
        }
        const Herp& operator=(string&& s)
        {
            cout<<"Move initialize operator\n";
            internal = s;
            return *this;
        }
};

int main()
{
    Herp a;
    stringstream ss;
    ss<<string("abcdef");
    a = ss.str();
    return 0;
}

Success time: 0 memory: 3428 signal: 0

Move initialize operator

More details here.

Upvotes: 0

MojaveWastelander
MojaveWastelander

Reputation: 66

For this case the idea is following:
1) ss returns a string which is a copy of internal representation of stringstream text, strings have an internal buffer, a pointer (which is why move make sense for strings, although there is also SSO)
2) As returned string is a temporary value when passes to emplace back it will move construct in-place a string object in vector, so there will be no copies of the internal buffer (there is just pointers and some integers swapping between temporary and new string, which are pretty cheap). Almost the same here will apply to push_back.

So for

  1. list.emplace_back(std::move(ss.str())
  2. list.emplace_back(ss.str())
  3. list.push_back(std::move(ss.str())
  4. list.push_back(ss.str())

all cases must do almost the same thing (means that they will not make a copy of temporary string buffer). As always profile and see what is better, but I doubt there is a tangible difference in this case.

Upvotes: 2

Related Questions