Writing output stream with out_buffer_provider - novalexei/nstream GitHub Wiki

Writing output stream with out_buffer_provider

The same reasoning we discussed in the previous section could also be applied to output streams. Can we just give a buffer to our output stream so that it could write into it directly bypassing unnecessary copying and additional buffering? Yes, we can! And this time it will be out_buffer_provider:

  • It should have type definitions for character type and for category (which in this case is always out_buffer_provider):
typedef out_buffer_provider    category;
typedef /* character type */   char_type;
  • It should implement the following methods:
std::pair<char_type*, std::size_t> get_out_buffer();
void flush(std::size_t size);

Method get_out_buffer returns std::pair containing a buffer for the stream to use and number of characters available in this buffer. If this method returns {nullptr, 0} it means that no more characters can be written into the output buffer.

Note that in C++17 this method can also return std::tuple<char_type*, std::size_t> or struct {char_type*, std::size_t}

Method flush is called when the stream is either flushed or closed. As the argument it receives the number of characters written to the buffer since last call to get_out_buffer or flush.

Example

Output buffer provider might be a bit tricky to write because it needs to take care of the buffer growth and keep track of the current written size. Let's write simple dynamically allocated buffer.

template<typename CharT>
class buffer_sink
{
public:
    typedef CharT                         char_type;
    typedef std::basic_string_view<CharT> string_view_type;
    typedef out_buffer_provider           category;

    explicit buffer_sink(std::size_t initial_capacity = 16) :
            _buffer{new char_type[initial_capacity]}, _size{0},
            _capacity{initial_capacity} {}

    ~buffer_sink() { delete[] _buffer; }

    std::pair<char_type*, std::size_t> get_out_buffer()
    {
        if (_provided_up_to < _capacity)
        {
            _size = _provided_up_to;
            _provided_up_to = _capacity;
            return {_buffer + _size, _capacity - _size};
        }
        char_type* old_buf = _buffer;
        std::size_t old_capacity = _capacity;
        _capacity *= 2;
        _buffer = new char_type[_capacity];
        std::copy(old_buf, old_buf+old_capacity, _buffer);
        delete[] old_buf;
        _size = _provided_up_to;
        _provided_up_to = _capacity;
        return {_buffer + old_capacity, _capacity - old_capacity};
    }

    void flush(int size) { _size += size; }

    string_view_type view() const { return string_view_type{_buffer, _size}; }
private:
    char_type* _buffer;
    std::size_t _size;
    std::size_t _capacity;
    std::size_t _provided_up_to = 0;
};

Small string optimization can be used here, but for the demonstration purpose this one will work just fine. This buffer provider can be used as usual:

int main()
{
    outstream<buffer_sink<char>> out;
    out << 123 << ' ' << 456;
    out.flush();
    std::cout << out->view() << std::endl;
    return 0;
}

To the next section: Input-output with shared devices

Back to the Tutorial

⚠️ **GitHub.com Fallback** ⚠️