3

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.

5
  • 2
    You won't need to call std::move() as ss.str() already returns an rvalue. Commented Mar 28, 2014 at 5:29
  • I need an xvalue out of it if I'm not mistaken. emplace_back perhaps can force this? Or something? My understanding is vague. (I think that may also be wrong. Emplace_back will build something in place using args to pass to the ctor of the type... that's effectively a copy operation. Do not want.) Commented Mar 28, 2014 at 5:30
  • ss.str() is a copy rvalue, which is moved by std::vector's move copy operator Commented Mar 28, 2014 at 5:33
  • @Julius What is a copy rvalue? An rvalue reference? Commented Mar 28, 2014 at 5:35
  • In case anyone else reading this is equally confused by emplace_back, here's a good discussion on it: stackoverflow.com/questions/4303513/push-back-vs-emplace-back Commented Mar 28, 2014 at 5:43

3 Answers 3

2

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Can you look at the colirus I posted on the comments to @Julius's answer? They indicate that a copy occurred. Maybe there is an unavoidable copy that always happens as part of str()? Maybe its a consequence of SSO.
ss.str() returns every time a new temporary string. In example code you called ss.str() three times and each time there is a different string returned with different internal buffer address, that's why in output you have different values.
0

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.

17 Comments

Thanks, this is still black magic to me, and I have been writing a lot of C++ lately. Still lots to learn. Now, I'm ~80% sure about this but please verify if you would be so kind: I should use emplace_back and then I can skip std::move, because it basically just forwards the ss.str() to the move ctor of std::string. Yes?
Is it also the case that using push_back also moves it? What if I wanted to push it to the vector, but I still wanted access to it (i.e. I actually wanted a copy...)
this works with push back. emplace back is specifically said to not use the move operator, as per (en.cppreference.com/w/cpp/container/vector/emplace_back)
Huh? So it's backwards? emplace_back to make a copy, push_back to make a move? How the hell am I supposed to make this deduction from reading the docs?
For push_back, it says the 2nd ctor is only used if T meets the requirements of MoveInsertable, and I have no idea if the value of ss.str() meets this criteria. Hence this question. For emplace_back, okay, circled back to my original conclusion about emplace_back, which is I can't use it (since I want to move, not copy or construct)
|
0

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.