Copy a Streambuf's Contents to a String

Copy a streambuf's contents to a string

I mostly don't like answers that say "You don't want X, you want Y instead and here's how to do Y" but in this instance I'm pretty sure I know what tstenner wanted.

In Boost 1.66, the dynamic string buffer type was added so async_read can directly resize and write to a string buffer.

How copy or reuse boost::asio::streambuf?

One can use boost::asio::buffer_copy() to copy the contents of Asio buffers. This can be convenient if, for example, one wishes to copy the contents of one streambuf to another streambuf.

boost::asio::streambuf source, target;
...
std::size_t bytes_copied = buffer_copy(
target.prepare(source.size()), // target's output sequence
source.data()); // source's input sequence
// Explicitly move target's output sequence to its input sequence.
target.commit(bytes_copied);

A similar approach can be used to copy from a streambuf into any type for which Asio supports mutable buffers. For example, copying content into a std::vector<char>:

boost::asio::streambuf source;
...
std::vector<char> target(source.size());
buffer_copy(boost::asio::buffer(target), source.data());

One notable exception is that Asio does not support returning a mutable buffer for std::string. However, one can still accomplish copying into std::string via iterators:

boost::asio::streambuf source;
...
std::string target{
buffers_begin(source.data()),
buffers_end(source.data())
};

Here is an example demonstrating copying contents from boost::asio::streambuf into various other types:

#include <iostream>
#include <string>
#include <vector>
#include <boost/asio.hpp>

int main()
{
const std::string expected = "This is a demo";

// Populate source's input sequence.
boost::asio::streambuf source;
std::ostream ostream(&source);
ostream << expected;

// streambuf example
{
boost::asio::streambuf target;
target.commit(buffer_copy(
target.prepare(source.size()), // target's output sequence
source.data())); // source's input sequence

// Verify the contents are equal.
assert(std::equal(
buffers_begin(target.data()),
buffers_end(target.data()),
begin(expected)
));
}

// std::vector example
{
std::vector<char> target(source.size());
buffer_copy(boost::asio::buffer(target), source.data());

// Verify the contents are equal.
assert(std::equal(begin(target), end(target), begin(expected)));
}

// std::string example
{
// Copy directly into std::string. Asio does not support
// returning a mutable buffer for std::string.
std::string target{
buffers_begin(source.data()),
buffers_end(source.data())
};

// Verify the contents are equal.
assert(std::equal(begin(target), end(target), begin(expected)));
}
}

efficient copy of data from boost::asio::streambuf to std::string

reserve doesn't mean what you think it means. You can work out for sure what it means by reading its documentation. It is not the same as resize; reserving space does not affect the container's size.

You need to actually insert elements into the string. Do this:

#include <algorithm>    // for copy
#include <iterator> // for back_inserter

_msg.reserve(_nBytesInMsg); // about to insert _nBytesInMsg elements

std::copy(
boost::asio::buffers_begin(buf),
boost::asio::buffers_begin(buf) + _nBytesInMsg,
std::back_inserter(_msg)
);

How to get part of a std::string into a streambuf without copying?

  1. istream::operator>>() mangles the contents of my streambufs (as you would expect given that it's 'formatted').

    Open your input stream with std::ios::binary flag and manipulate it with is >> std::noskipws

  2. For example, if I want to get a substring of a string into a streambuf, how do I do that without creating a copy of the string? A basic all-in-all-out transfer can be accomplished like

    Try like

     outstream.write(s.begin()+start, length);

    Or use boost::string_ref:

     outstream << boost::string_ref(s).instr(start, length);

  3. And there doesn't seem to be any way of directly using string::iterators to dump part of a string into a streambuf:

     std::copy(it1, it2, ostreambuf_iterator<char>(os));
  4. Re. parsing the message lines:

    You can split into iterator ranges with iter_split.

    You can parse an embedded grammar on the fly with boost::spirit::istream_iterator

Converting boost streambuf to string_ref

I think this is, indeed, not correct, because the streambuf may have several non-contiguous regions.

So you need to copy anyways. Alternatively, just read into a fixed buffer instead. Of course, this requires you to know the maximum size ahead of time, or read in several steps.

By the way, by taking a const& you risk creating a string_ref that refers to a temporary. Always try to be explicit about life time expectations.

Convert std::ostream to std::string

  • If you have a a stringstream or a ostreamstring, object or reference, just use the ostringstream::str() method that does exactly what you want: extract a string. But you know that and you apparently have a ostream object, not a stringstream nor a ostreamstring.

  • If your ostream reference is actually a ostringstream or a stringstream, use ostream::rdbuf to get a streambuf, then use this post to see how to extract a string.

Example:

#include <iostream>
#include <sstream>

std::string toString( std::ostream& str )
{
std::ostringstream ss;
ss << str.rdbuf();
return ss.str();
}

int main() {

std::stringstream str;
str << "Hello";

std::string converted = toString( str );

std::cout << converted;

return 0;
}

Live demo: http://cpp.sh/6z6c

  • Finally, as commented by M.M, if your ostream is a ofstream or an iostream, most likely it clears its buffer so you may not be able to extract the full content as proposed above.

Stream from std::string without making a copy?

The reason you're having these problem is that std::string isn't really suited to what you're doing. A better idea is to use vector of char when passing around raw data. If its possible, I would just change everything to use vector, using vector::swap and references to vectors as appropriatte to eliminate all your copying. If you like the iostreams/streambuf api, or if you have to deal with something that takes a streambuf, it would be trivial to create your own streambuf that uses a vector, like yours. It would effectively do the same thing that you do with the same issues as listed in the other answers, but you wouldn't be violating the class's contract.

Otherwise, I think what you've got is probably the best way forward short of passing around an istringstream everywhere.

Convert std::string to boost::asio::streambuf

The following code shows how to convert between std::string and boost::asio::streambuf, see it online at ideone:

#include <iostream>
#include <boost/asio/streambuf.hpp>
#include <boost/asio/buffer.hpp>

int main()
{
/* Convert std::string --> boost::asio::streambuf */
boost::asio::streambuf sbuf;
std::iostream os(&sbuf);
std::string message("Teststring");
os << message;

/* Convert boost::asio::streambuf --> std::string */
std::string str((std::istreambuf_iterator<char>(&sbuf)),
std::istreambuf_iterator<char>());

std::cout << str << std::endl;
}


Related Topics



Leave a reply



Submit