0

Basically I implement a traditional file-descriptor-based streambuffer for istream operations. The implementation overrides the sync and underflow functions, while in sync the buffer will be automatically enlarged if necessary (just like vector).

class InputStreamBuffer : public std::basic_streambuf<char> {

  private:

    // small stack buffer optimization
    constexpr static size_t _STACK_BUFSZ = 128;

    static_assert(_STACK_BUFSZ >= 1, "Stack buffer size should be at least 1");

    char_type  _stk [_STACK_BUFSZ];
    char_type* _buf {_stk};

    size_t _size {_STACK_BUFSZ};

    const int _fd {-1};

  public:

    InputStreamBuffer(const int);
    ~InputStreamBuffer();

  protected:

    int sync() override;
    int_type underflow() override;
};

InputStreamBuffer::InputStreamBuffer(const int fd) :
  _fd {fd} {
  setg(_buf, _buf, _buf);
}

InputStreamBuffer::~InputStreamBuffer() {
  if(_buf != _stk) {
    std::free(_buf);
  }
}

int InputStreamBuffer::sync() {

  auto success = int {0};

  while(1) {

    size_t empty = gptr() - eback();
    size_t avail = egptr() - gptr();

    // Before we issue the read, make sure we have enough space.
    if(egptr() == eback() + _size) {
      // Reuse the empty region.
      if(empty > _size / 2) {
        std::memmove(eback(), gptr(), avail);
      }
      // Double the array size.
      else {
        _size = _size * 2;
        auto chunk = static_cast<char_type*>(std::malloc(_size*sizeof(char_type)));
        std::memcpy(chunk, gptr(), avail);
        if(_buf != _stk) std::free(_buf);
        _buf = chunk;
      }
      setg(_buf, _buf, _buf + avail);
    }

    // Read the data.
    issue_read:
    auto ret = ::read(_fd, egptr(), _size - avail);

    if(ret == -1) {
      if(errno == EINTR) {
        goto issue_read;
      }
      if(errno != EAGAIN && errno != EWOULDBLOCK) {
        success = -1;
      }
      break;
    }
    else if(ret == 0) {
      break;
    }

    setg(eback(), gptr(), egptr() + ret);
  }

  return success;
}

InputStreamBuffer::int_type InputStreamBuffer::underflow() {
  int success = sync();
  if(success == -1 || gptr() == egptr()) return traits_type::eof();
  return *(gptr());
}

The idea here is to use this streambuffer with non-blocking io. The IO multiplexer will autonomously call sync and invoke the user-registered callbacks to perform istream-style operations. Simply put, here are some of my questions:

  1. The input streambuffer has certain type of operations called "putback". What is the potentially benefit of using the putback?

  2. The ::read will return 0 when reaching EOF, which is a very useful indicator for signaling the connection status of socket-based IO. What is the best practice to handle this in sync? My current implementation simply skips this.

  3. Any comments that can improve the implementation will be greatly appreciated!

Jes
  • 2,614
  • 4
  • 25
  • 45

1 Answers1

0

According to Mr. Edd's very helpful introduction to custom stream buffer implementation,

Input stream buffers, written for use with istreams, tend to be a little bit more complex than output buffers, written for ostreams. This is because we should endeavor to allow the user to put characters back in to the stream, to a reasonable degree, which is done through the std::istream's putback() member function. What this means is that we need to maintain a section at the start of the array for put-back space. Typically, one character of put-back space is expected, though there's no reason we shouldn't be able to provide more, in general.

See also https://stackoverflow.com/a/39078167/1399272.

Community
  • 1
  • 1
Bondolin
  • 2,793
  • 7
  • 34
  • 62