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.

Steven Lu
  • 41,389
  • 58
  • 210
  • 364
  • 2
    You won't need to call `std::move()` as `ss.str()` already returns an rvalue. – Peter R Mar 28 '14 at 05: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.) – Steven Lu Mar 28 '14 at 05:30
  • ss.str() is a copy rvalue, which is moved by std::vector's move copy operator – Jules G.M. Mar 28 '14 at 05:33
  • @Julius What is a copy rvalue? An rvalue reference? – Steven Lu Mar 28 '14 at 05:35
  • In case anyone else reading this is equally confused by emplace_back, here's a good discussion on it: http://stackoverflow.com/questions/4303513/push-back-vs-emplace-back – Liam Mar 28 '14 at 05:43

3 Answers3

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.

Community
  • 1
  • 1
  • 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. – Steven Lu Mar 28 '14 at 07:04
  • 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. – MojaveWastelander Mar 28 '14 at 09:18
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.

Jules G.M.
  • 3,624
  • 1
  • 21
  • 35
  • 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? – Steven Lu Mar 28 '14 at 05:52
  • 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...) – Steven Lu Mar 28 '14 at 05:54
  • this works with push back. emplace back is specifically said to not use the move operator, as per (http://en.cppreference.com/w/cpp/container/vector/emplace_back) – Jules G.M. Mar 28 '14 at 05:54
  • 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? – Steven Lu Mar 28 '14 at 05:54
  • 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) – Steven Lu Mar 28 '14 at 05:56
  • well in http://en.cppreference.com/w/cpp/container/vector/push_back and http://en.cppreference.com/w/cpp/container/vector/emplace_back the first few lines mention this explicitely – Jules G.M. Mar 28 '14 at 05:56
  • Well. alright... backing up a bit, `emplace_back` **doesn't even make any sense to use** in this situation. So my question had wrong assumptions to begin with. I'm basically just confused now about how `push_back(ss.str())` results in a move, but `const string& s = ss.str; list.push_back(s)` is a copy. How does it know to make the first statement a move? Just based on the temporariness of the rvalue? – Steven Lu Mar 28 '14 at 05:58
  • I'm fairly sure any value returned by copy by a function is MoveInsertable... this is just my understanding though, you should test it – Jules G.M. Mar 28 '14 at 06:00
  • I still have absolutely no working understanding of what this concept of "return by copy" is. What is that? Does it have anything to do with "return by value"? – Steven Lu Mar 28 '14 at 06:01
  • const string& s = ss.str; list.push_back(s); is a copy because move constructors aren't const, as they modify the original object (in this case, by swapping with it) – Jules G.M. Mar 28 '14 at 06:01
  • return by copy is a more clear way to understand what goes on when one returns by value in c++, namely, that a temp object is created by copy on the return value before it goes off scope – Jules G.M. Mar 28 '14 at 06:03
  • Thanks for continuing to answer the questions even though I am too dumb to understand how this all works. What happens then if I do this? `string& s = ss.str; list.push_back(s);` What's in `ss` at this point? Empty/garbage? How about after `list.push_back(ss.str())`? `ss` should be garbage after that, right? (That would bite many an unexacting programmer in the ass) – Steven Lu Mar 28 '14 at 06:12
  • http://coliru.stacked-crooked.com/a/ca4e4731eab3b460 This is further bewilderment. It uses the move initialize operator, but there are two copies of `abcdef` now. WHAT! – Steven Lu Mar 28 '14 at 06:18
  • This doesn't clear it up either.. http://coliru.stacked-crooked.com/a/25f10bbce9a6d901 – Steven Lu Mar 28 '14 at 06:20
  • Ahah so emplace_back is not a diffrent kind of push_back, it should be seen as a weird alternate constuctor for the internal type of the collection, ie vector:: emplace_back(constrArgN ..) will create an object on the vector by calling the object's constructor with the same args passed to emplace_back, here constrArgsN. It only forwards the arguments to the constructor, it shouldnt receive the object itself – Jules G.M. Mar 28 '14 at 07:15
  • And nope, push_back (ss.str ()) only makes the copy returned by that specific call of ss.str () be in an undefined state, not the string in ss itself – Jules G.M. Mar 28 '14 at 07:19
  • Assinging your return value to a lvalue ref makes it a string& instead of a string&&. The copy constructor would be called unless you std::move'd it – Jules G.M. Mar 28 '14 at 07:22
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());
jrok
  • 54,456
  • 9
  • 109
  • 141