1

I have a very simple stream buffer setup that forwards the bytes from a stream to the buffer. When I print I get a weird output, namely "HellHelllo," instead of "Hello". Maybe my eyes are tired, but I can't find the problem here. Why am I getting is output?

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <sstream>

class io_buffer : public std::streambuf
{
public:
    io_buffer(std::istream& is, int buf_size = 4)
        : size(buf_size) , is_(is) , buffer(std::max(buf_size, 1))
    {
        char* end = &buffer.front() + buffer.size();
        setg(end, end, end);
    }

    int_type underflow()
    {
        if (gptr() < egptr())
            return traits_type::to_int_type(*gptr());

        if (is_)
        {
            std::copy_n(std::istreambuf_iterator<char>(is_),
                        size, std::back_inserter(buffer));

            char* beg = &buffer.front();
            setg(beg, beg, beg + buffer.size());
            return traits_type::to_int_type(*gptr());
        }
        return traits_type::eof();
    }
private:
    int size;
    std::istream& is_;
    std::vector<char> buffer;
};

int main()
{
    std::istringstream oss("Hello, World");
    io_buffer buf(oss);

    std::istream is(&buf);
    std::string str;

    is >> str;
    std::cout << str << std::endl; // "HellHelllo,"
}
template boy
  • 10,230
  • 8
  • 61
  • 97

2 Answers2

2

You initialize the buffer with (maybe 4) zeros

buffer(std::max(buf_size, 1))

and append the content to the buffer without clearing the buffer.

You may change it to:

int_type underflow()
{
    if (gptr() < egptr())
        return traits_type::to_int_type(*gptr());

    if (is_)
    {
        char* beg = buffer.data();
        char* end = std::copy_n(
            std::istreambuf_iterator<char>(is_),
            size, beg);
        setg(beg, beg, end);
        return traits_type::to_int_type(*gptr());
    }
    return traits_type::eof();
}

You still have a bug here: std::copy_n might ask for too many characters.

Make it:

int_type underflow()
{
    if (gptr() < egptr())
        return traits_type::to_int_type(*gptr());

    if (is_)
    {
        char* beg = buffer.data();
        char* end = beg;
        char* limit = beg + buffer.size();
        std::istreambuf_iterator<char> in(is_);
        std::istreambuf_iterator<char> eos;
        for( ; end < limit && in != eos; ++end, ++in)
            *end = *in;
        setg(beg, beg, end);
        if(beg < end)
            return traits_type::to_int_type(*gptr());
    }
    return traits_type::eof();
}

And eliminate the member size.

1

I think this is because underflow gets called twice.

This could be a bug.

Community
  • 1
  • 1
Pratik Deoghare
  • 35,497
  • 30
  • 100
  • 146
  • The buffer size is a default argument. And I'm petty sure it was made small so that he could test out `underflow()`. – David G Mar 01 '14 at 04:14
  • Yes but it is set to `4` and bigger number is needed. – Pratik Deoghare Mar 01 '14 at 04:14
  • He intentionally set it to 4 so that he could test `underflow()`. – David G Mar 01 '14 at 04:15
  • @0x499602D2 Oh I see. – Pratik Deoghare Mar 01 '14 at 04:18
  • Running your program using Visual C++ 2013, an assertion is raised for a very simple case. You need to fix your sizing and limits in your class. For example, if I give your stream a small string, but give the buffer a size larger than the string size, you will go out of bounds during the copy_n call. For example `std::istringstream oss("Hi"); io_buffer(oss);` – PaulMcKenzie Mar 01 '14 at 04:40
  • In addition, a back_inserter() being used on a container that is non-empty is a red flag. Usually back_inserter() during an algorithm call is used to fill empty containers. – PaulMcKenzie Mar 01 '14 at 04:42