Someway to buffer or wrap cin so I can use tellg/s

2019-09-14 10:13发布

Is there any way to add buffering to cin so that I can effectively use tellg and seekg on that istream? (I only need to go back about 6 characters.) Or is there perhaps some way to wrap the stream with a (perhaps custom) istream object that acts as a buffered pipe that would allow me to use tellg/seekg to revert the stream position a few characters? It might look like this:

BufferedIStream bis(cin);
streampos pos = bis.tellg();
MyObjectType t = getObjectType(bis);
bis.seekg(pos);

As a work-around, I'm currently reading cin to EOF into a string, and transferring that string to an istringstream, but this has numerous negative side-affects that I'd like to avoid.

The only other thing I can think to do is to overload all my scan/read functions on all my data classes with a private version (only used by the factory) where the header is assumed to already have been consumed, so that I can eliminate the need for tellg/seekg altogether. This would work fine, but would introduce a fair-amount of ugliness. In comparison, tellg/seekg is isolated to my factory and is just two lines of code. I hate to dump it.

标签: c++ cin istream
1条回答
叼着烟拽天下
2楼-- · 2019-09-14 10:43

You can create a filtering stream buffer, i.e., a class derived from std::streambuf. To support buffered reading you'd override underflow() to fill the next buffer of character once the input characters are consumed. To support limited seeking the previous buffer would not be discarded but rather partly retained. In addition you'd override seekoff().

Something like this should do the trick:

#include <iostream>
#include <streambuf>
#include <string>
#include <cstdlib>
#include <cstring>

class bufferbuf
    : public std::streambuf {
    enum { size = 2000, half = size / 2 };
    char            buffer[size];
    std::streambuf* sbuf;
    std::streamoff  base;
public:
    bufferbuf(std::streambuf* sbuf): sbuf(sbuf), base() {
        auto read = sbuf->sgetn(this->buffer, size);
        this->setg(this->buffer, this->buffer, this->buffer + read);
    }
    int underflow() {
        if (this->gptr() == this->buffer + size) {
            std::memmove(this->eback(), this->eback() + half, half);
            base += half;
            auto read = sbuf->sgetn(this->eback() + half, half);
            this->setg(this->eback(), this->eback() + half, this->eback() + half + read);
        }
        return this->gptr() != this->egptr()
            ? traits_type::to_int_type(*this->gptr())
            : traits_type::eof();
    }
    std::streampos seekoff(off_type                offset,
                           std::ios_base::seekdir  whence,
                           std::ios_base::openmode which) override {
        if (this->gptr() - this->eback() < -offset
            || this->egptr() - this->gptr() < offset
            || whence != std::ios_base::cur
            || !(which & std::ios_base::in)) {
            return pos_type(off_type(-1));
        }
        this->gbump(offset);
        return pos_type(this->base + (this->gptr() - this->eback()));
    }
    std::streampos seekpos(pos_type pos, std::ios_base::openmode which) override {
        if (off_type(pos) < this->base
            || this->base + (this->egptr() - this->eback()) < off_type(pos)
            || !(which & std::ios_base::in)) {
           return pos_type(off_type(-1));
        }
        this->setg(this->eback(), this->eback() + (off_type(pos) - this->base), this->egptr());
        return pos_type(base + (this->gptr() - this->eback()));
    }
};

int main() {
    bufferbuf buf(std::cin.rdbuf());
    std::istream in(&buf);
    // ...
    std::string s0, s1;
    bool relative(false);
    if (relative) {
        while (in >> s0
               && (in.seekg(-int(s0.size()), std::ios_base::cur), in >> s1)) {
            std::cout << "read "
                      << "s0='" << s0 << "' " << "s1='" << s1 << "'\n";
        }
    }
    else {
        for (std::streampos pos = in.tellg();
             in >> s0 && (in.seekg(pos), in >> s1); pos = in.tellg()) {
            std::cout << "read "
                      << "s0='" << s0 << "' " << "s1='" << s1 << "'\n";
        }
    }
}

The code above works with a couple of simple test cases. It demonstrates uses of both relative and absolute positioning. Gennerally, I find it useless to seek in streams as generally every interesting lexing can be done with just one character look-ahead. As a result, I may have missed something in the context of position. However, I expect the above code to work proper.

查看更多
登录 后发表回答