-
Notifications
You must be signed in to change notification settings - Fork 1
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.
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