Question

I have an ostream and data has been written to it. Now I want that data in the form of a char array. Is there a way to get the char buffer and its size without copying all of the bytes? I mean, I know I can use ostringstream and call str().c_str() on it but that produces a temporary copy.

Was it helpful?

Solution

I guess this is what you're looking for - a stream buffer that returns a pointer to its buffer:

#include <iostream>
#include <vector>
#include <string>

class raw_buffer : public std::streambuf
{
public:
    raw_buffer(std::ostream& os, int buf_size = 256);
    int_type overflow(int_type c) override;
    std::streamsize showmanyc() override;
    std::streamsize xsputn(const char_type*, std::streamsize) override; 
    int sync() override;
    bool flush();
    std::string const& str() const;
private:
    std::ostream& os_;
    std::vector<char> buffer;
    std::string aux;
};

Now str() is simple. It returns a pointer to the underlying buffer of the auxillary buffer:

std::string const& raw_buffer::str() const
{
    return aux;
}

The rest of the functions are the usual implementations for a stream buffer. showmanyc() should return the size of the auxiliary buffer (aux is just a running total of the entire buffer, buffer on the other hand is the size specified at construction).

For example, here is overflow(), which should update both buffers at same time but still treat buffer as the primary buffer:

raw_buffer::int_type raw_buffer::overflow(raw_buffer::int_type c) override
{   
    if (os_ && !traits_type::eq_int_type(c, traits_type::eof()))
    {
        aux += *this->pptr() = traits_type::to_char_type(c);
        this->pbump(1);

        if (flush())
        {
            this->pbump(-(this->pptr() - this->pbase()));
            this->setp(this->buffer.data(),
                       this->buffer.data() + this->buffer.size());
            return c;
        } 
    }
    return traits_type::eof();
}

flush() is used to copy the contents of buffer to the stream (os_), and sync() should be overrided to call flush() too.

xsputn also needs to be overrided to write to aux as well:

std::streamsize raw_buffer::xsputn(const raw_buffer::char_type* str, std::streamsize count) override
{
    for (int i = 0; i < count; ++i)
    {
        if (traits_type::eq_int_type(this->sputc(str[i]), traits_type::eof()))
            return i;
        else
            aux += str[i];
    }
    return count;
}

Now we can put this together with a customized stream:

class raw_ostream : private virtual raw_buffer
                  , public std::ostream
{
public:
    raw_ostream(std::ostream& os) : raw_buffer(os)
                                  , std::ostream(this)
    { }

    std::string const& str() const
    {
        return this->raw_buffer::str();
    }

    std::streamsize count()
    {
        return this->str().size();
    }
};

It can be used like this:

int main()
{
    raw_ostream rostr(std::cout);
    rostr << "Hello, World " << 123 << true << false;

    auto& buf = rostr.str();
    std::cout << buf;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top