How to Reuse an Ostringstream

How to reuse an ostringstream?

I've used a sequence of clear and str in the past:

// clear, because eof or other bits may be still set. 
s.clear();
s.str("");

Which has done the thing for both input and output stringstreams. Alternatively, you can manually clear, then seek the appropriate sequence to the begin:

s.clear();
s.seekp(0); // for outputs: seek put ptr to start
s.seekg(0); // for inputs: seek get ptr to start

That will prevent some reallocations done by str by overwriting whatever is in the output buffer currently instead. Results are like this:

std::ostringstream s;
s << "hello";
s.seekp(0);
s << "b";
assert(s.str() == "bello");

If you want to use the string for c-functions, you can use std::ends, putting a terminating null like this:

std::ostringstream s;
s << "hello";
s.seekp(0);
s << "b" << std::ends;
assert(s.str().size() == 5 && std::strlen(s.str().data()) == 1);

std::ends is a relict of the deprecated std::strstream, which was able to write directly to a char array you allocated on the stack. You had to insert a terminating null manually. However, std::ends is not deprecated, i think because it's still useful as in the above cases.

how to reuse stringstream

You didn't clear() the stream after calling str(""). Take another look at this answer, it also explains why you should reset using str(std::string()). And in your case, you could also reset the contents using only str(szLine).

If you don't call clear(), the flags of the stream (like eof) wont be reset, resulting in surprising behaviour ;)

Clear and reuse a stringstream for multiple conversions

You can call the str(s) method to initialize an std::istringstream to a new string. You should use std::istringstream if all you're doing is converting from a string.

If the previous conversion resulted in an error you will also need clear(), to clear its error state.

So your example would be:

istringstream ss(hour);
ss >> i_hour;
ss.clear();
ss.str(minute);
ss >> i_minute;

reusing a stringstream without re-allocation

I did some investigations and experiments using clang with this code:

Test code

class LogHelper {
public:
~LogHelper() {
std::cout << out.str() << '\n';
}

std::ostream &stream() {
return out;
}

private:
std::ostringstream out;
};

#define LOG() LogHelper().stream() << __FUNCTION__ << '(' << __LINE__ << ")"
#define VAR(x) ", " #x "[" << x << ']'

class MyAllocator : public std::allocator<char> {
public:
using base = allocator<value_type>;
using base::allocator;

value_type* allocate( std::size_t n, const void * hint) {
LOG() << VAR(n);
return base::allocate(n, hint);
}

value_type* allocate( std::size_t n ) {
LOG() << VAR(n);
return base::allocate(n);
}

void deallocate( value_type* p, std::size_t n ) {
LOG() << VAR(n);
base::deallocate(p, n);
}
};

using MySStream = std::basic_stringstream<char, std::char_traits<char>, MyAllocator>;
using MyString = std::basic_string<char, std::char_traits<char>, MyAllocator>;

int main() {
MySStream ss; // (MyString(255, '\0'));
ss.clear();

int x;
ss << "423";
ss << " 423";

LOG();
ss << " 423jlfskdfjl jfsd sdfdsfkdf dsfg dsfg dfg dfg dsfg df gdf gdfg dsfg dsfgdsfgds";
LOG();
ss >> x;

ss.clear();
ss.str({});

ss.seekg(0);
ss.seekp(0);

ss << "1";
ss >> x;

std::cout << x << std::endl;
LOG();

return 0;
}
  • your example with longing allocations clang, visual studio, gcc 7.3 ignores custom allocator, gcc 8.x do not compile
main(55)
allocate(34), n[48]
allocate(34), n[96]
deallocate(39), n[48]
main(57)
1
main(70)
deallocate(39), n[96]
  • preset long string on stream clang, visual studio
allocate(34), n[256]
allocate(34), n[256]
deallocate(39), n[256]
main(55)
main(57)
1
main(70)
deallocate(39), n[256]

And I have couple findings

  1. clang and visual behave in same meaner, gcc has some stang issues with this code.
  2. std::basic_stringstream string buffers always grows, never shrinks
  3. std::basic_stringstream sucks. You can't reserve string size or buffer size, like in case std::string. Custom allocator can be passed only by type, you can't provide allocator by object.
  4. To reduce allocations you have to set large string at the begging, then until you you will not succeed its capacity re-allocation will not happen (second example).
  5. providing a custom allocator doesn't help a lot and it adds boiler plate code when fetching result string. In my examples it is used mainly to log allocations and deallocations.
  6. ss.str({}); do not cause allocation. Here small string optimization helps

Conclusions:

  1. you can safely do ss.str({}); as recommended in linked by you SO answer and it will not cause allocation. Here small string optimization helps and the fact that
  2. custom allocator is not very helpful
  3. setting large dummy string at begging is quite effective evil hack
  4. finding alternative should be better approach (maybe boost - I didn't test it)
  5. Point 1 and your question shows that you didn't do any measurements and your question is based on personal assumptions.

How do you clear a std::ostringstream instance so it can be reused?

Use stream.str(""); to make the underlying string an empty one. Better yet, don't reuse objects. In C++ the philosophy is to make an object when you need it and dispose of it when you're done:

{
std::ostringstream oss;
oss << 10;
std::cout << oss.str();
}

{
std::ostringstream oss;
oss << 20.5;
std::cout << oss.str();
}

Or better yet:

std::cout << static_cast<std::ostringstream&>(std::ostringstream() << 10).str();

How to clear ostringstream


s.str("");
s.clear();

The first line is required to reset the string to be empty; the second line is required to clear any error flags that may be set. If you know that no error flags are set or you don't care about resetting them, then you don't need to call clear().

Usually it is easier, cleaner, and more straightforward (straightforwarder?) just to use a new std::ostringstream object instead of reusing an existing one, unless the code is used in a known performance hot spot.

How do I use std::ends to overwrite the string in an std::ostringstream?

actually it prints

Sample Image

note the [NUL] which corresponds to the std::ends and did overwrote the comma after the 3

the ostringstream::str() function returns a container which tracks its length and ignores any possible line ending charakter.

so to get a format which respects the string ending character you can e.g. use the char * (or wchar_t* if compiling with unicode support) representation obtained via std::string::c_str() like this foo.str().c_str();

Reverse an ostringstream by line

Using your original code with C++98:

  ostringstream ss;
ss << "(1,2)\n" << "(1,3)\n" << "(1,4)\n" ;
cout << ss.str();

//assign a string to the contents of the ostringstream:
string rawlines = ss.str();

//now create an input stringstream with the value of the rawlines
istringstream iss(rawlines);

string temp;//just a temporary object used for storage
vector<string> lines;//this is where your lines will be held

//now iterate over the stream and store the contents into the vector `lines`:
while(getline(iss, temp)) {
lines.push_back(temp);
}

//now reverse the contents:
reverse(lines.begin(), lines.end());

//see what's inside:
for (vector<string>::const_iterator it = lines.begin(); it != lines.end(); ++it) {
cout << *it << endl;
}

This will print:

(1,4)
(1,3)
(1,2)

As desired

NOTE: This strips the newlines from the the original string.
And, this requires:

//for `getline`:
#include <cstdlib>
//for `reverse`:
#include <algorithm>
//for `string`:
#include <string>
//for `vector`:
#include <vector>

ostringstream, .str(), and reseting/clearing the stream

These are different functions.

oss.str() (without parameters) returns a copy to the stream's underlying string, but oss.str("") sets its underlying string to the value you passed (here an empty string: "").

So calling both clear() and str("") on a std::stringstream actually kind of "resets" it.

Notice the two signatures on cppreference



Related Topics



Leave a reply



Submit