4

To understand how input streams work I designed 2 of the following classes:

#include <iostream>

class my_streambuf: public std::streambuf
{
private:
  std::streambuf* buffer;

  char ch;

protected:

  virtual std::streambuf::int_type underflow()
  {
    std::streambuf::int_type result = buffer->sbumpc();

    if (result != traits_type::eof())
    {
      ch = traits_type::to_char_type(result);

      setg(&ch, &ch, &ch + 1);
    }

    return result;
  }

public:
  my_streambuf(std::streambuf* buffer) : buffer(buffer) {};

  virtual ~my_streambuf() {};
};

class my_istream: public std::istream
{
public:
  my_istream(std::istream& stream) : std::istream(new my_streambuf(stream.rdbuf())) {};

  virtual ~my_istream()
  {
    delete rdbuf();
  }
};

int main()
{
  char s[32];
  my_istream is(std::cin);

  is >> s;
  std::cout << s;
  return 0;
}

Which work fine, until I change the logic of underflow method. The primary goal is to save data in c-string valiable s which differs from user-input. To make a simple test, I changed the underflow method to be the following:

virtual std::streambuf::int_type underflow()
{
  std::streambuf::int_type result = buffer->sbumpc();

  if (result != traits_type::eof())
  {
    result = traits_type::to_int_type('+'); // <-- this was added

    ch = traits_type::to_char_type(result);

    setg(&ch, &ch, &ch + 1);
  }

  return result;
}

With the idea being to make the method return only + symbols instead of user-input chars. So for example if input is 123, I expect +++ to be stored in variable s. And that does not work. Console hangs as if it is waiting more input. Only a certain amount of keypressing (or sending EOF) helps.

What am I missing here?


As was pointed out by @ferosekhanj, the problem was the missing newline, which was not returned by the modified version of underflow to the caller. So in order for the code to work properly it has to be returned. This version of the method works fine.

virtual std::streambuf::int_type underflow()
{
  std::streambuf::int_type result = buffer->sbumpc();

  if ((result != traits_type::eof()) && !traits_type::eq(traits_type::to_char_type(result), '\n'))
  {
     result = traits_type::to_int_type('+');

     ch = traits_type::to_char_type(result);

     setg(&ch, &ch, &ch + 1);
  }

  return result;
}
HighPredator
  • 790
  • 4
  • 21

1 Answers1

7

From my old C++ experience a stream buf is the underlying buffer for the stream. When the stream needs more data it calls underflow. Inside this method you are suppose to read from your source and setg. When the stream has data to be written back to the source it calls overflow. Inside this method you read from the stream,write back to your source and setp. For example if you are reading the data from a socket in your streambuf

socketbuf::int_type socketbuf::underflow(){
  int bytesRead = 0;
  try{
    bytesRead = soc->read(inbuffer,BUFFER_SIZE-1,0);
    if( bytesRead <= 0 ){
      return traits_type::eof();
    }
  }catch(IOException ioe){
    cout<<"Unable to read data"<<endl;
    return traits_type::eof();
  }
  setg(inbuffer,inbuffer,inbuffer+bytesRead);
  return traits_type::to_int_type(inbuffer[0]);
}

socketbuf::int_type socketbuf::overflow(socketbuf::int_type c){
  int bytesWritten = 0;
  try{
    if(pptr() - pbase() > 0){
      bytesWritten = soc->write(pbase(),(pptr() - pbase()),0);
      if( bytesWritten <= 0 )  return traits_type::not_eof(c);
    }
  }catch(IOException ioe){
    cout<<"Unable to write data"<<endl;
    return traits_type::eof();
  }
  outbuffer[0] = traits_type::to_char_type(c);
  setp(outbuffer,outbuffer+1,outbuffer+BUFFER_SIZE);
  return traits_type::not_eof(c);
}

Now coming to your code, you added

result = traits_type::to_int_type('+'); // <-- this was added

A stream reads a string until it sees a LF(line feed). So when the LF character come you are over writing that with a '+' so the stream will wait (for LF) forever.By adding this check your code should do what you are expecting. output '+++' if you input 'abc'

if (result != 10)// <-- add this in addition
    result = traits_type::to_int_type('+'); // <-- this was added

Hope it helps you.

ferosekhanj
  • 1,086
  • 6
  • 11
  • If I'm not much mistaken, only `cin` reads a string until it sees a line feed. Other streams do this up until `EOF`. – HighPredator Aug 22 '16 at 11:28
  • what do you mean by other streams? cin is just an instance of std:istream – ferosekhanj Aug 22 '16 at 11:54
  • I mean that to my knowledge this is implemented `in` cin, not the stream class. – HighPredator Aug 22 '16 at 12:01
  • cin is not a class. It is an instance of istream class. And istream extraction operator (>>) will read a string until it sees a line feed. – ferosekhanj Aug 22 '16 at 12:09
  • Can you post the code? It works for me in windows + VS2013. – ferosekhanj Aug 22 '16 at 12:35
  • http://pastebin.com/bGjqJtgV Same behaviour as described in OP in both MSVS2010 and g++ on cygwin with c++14 key. – HighPredator Aug 22 '16 at 12:39
  • The code from your pastebin works for me in my VS2013 without any problem. It even works in http://cpp.sh/2ogue online cpp compiler. What is not working for you? – ferosekhanj Aug 22 '16 at 12:57
  • "Console hangs as if it is waiting more input. Only a certain amount of keypressing (or sending EOF) helps." – HighPredator Aug 22 '16 at 13:13
  • Now you have to debug your thing. The code works for me. Check if you are still executing a older binary. – ferosekhanj Aug 23 '16 at 03:45
  • Debugging didn't help. However, I changed the `if-statement` to the following `if ((result != traits_type::eof()) && !traits_type::eq(traits_type::to_char_type(result), '\n'))` and it worked. I'm going to add this note to OP and mark your answer as correct, since the reason was exactly like you said. – HighPredator Aug 23 '16 at 06:02