Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a QFile with std::iostream?

Tags:

iostream

stl

qt

Is it possible to use a QFile like a std::iostream? I'm quite sure there must be a wrapper out there. The question is where?

I have another libs, which requires a std::istream as input parameter, but in my program i only have a QFile at this point.

like image 563
Andreas Roth Avatar asked Mar 05 '11 14:03

Andreas Roth


2 Answers

Here's a good guide for subclassing std::streambuf to provide a non-seekable read-only std::istream: https://stackoverflow.com/a/14086442/316578

Here is a simple class based on that approach which adapts a QFile into an std::streambuf which can then be wrapped in an std::istream.

#include <iostream>
#include <QFile>

constexpr qint64 ERROR = -1;
constexpr qint64 BUFFER_SIZE = 1024;

class QFileInputStreamBuffer final : public std::streambuf {
private:
    QFile &m_file;
    QByteArray m_buffer;
public:
    explicit QFileInputStreamBuffer(QFile &file)
        : m_file(file),
          m_buffer(BUFFER_SIZE, Qt::Uninitialized) {
    }

    virtual int underflow() override {
        if (atEndOfBuffer()) {
            // try to get more data
            const qint64 bytesReadIntoBuffer = m_file.read(m_buffer.data(), BUFFER_SIZE);
            if (bytesReadIntoBuffer != ERROR) {
                setg(m_buffer.data(), m_buffer.data(), m_buffer.data() + bytesReadIntoBuffer);
            }
        }
        if (atEndOfBuffer()) {
            // no more data available
            return std::char_traits<char>::eof();
        }
        else {
            return std::char_traits<char>::to_int_type(*gptr());
        }
    }

private:
    bool atEndOfBuffer() const {
        return gptr() == egptr();
    }
};

If you want to be able to more things like seek, write, etc., then you'd need one of the other more complex solutions here which override more streambuf functions.

like image 52
Anthony Hayward Avatar answered Oct 20 '22 03:10

Anthony Hayward


I came up with my own solution using the following code:

#include <ios>
#include <QIODevice>

class QStdStreamBuf : public std::streambuf
{
public:
    QStdStreamBuf(QIODevice *dev) : std::streambuf(), m_dev(dev)
    {
        // Initialize get pointer.  This should be zero so that underflow is called upon first read.
        this->setg(0, 0, 0);
    }

protected:
virtual std::streamsize xsgetn(std::streambuf::char_type *str, std::streamsize n)
{
    return m_dev->read(str, n);
}

virtual std::streamsize xsputn(const std::streambuf::char_type *str, std::streamsize n)
{
    return m_dev->write(str, n);
}

virtual std::streambuf::pos_type seekoff(std::streambuf::off_type off, std::ios_base::seekdir dir, std::ios_base::openmode /*__mode*/)
{
    switch(dir)
    {
        case std::ios_base::beg:
            break;
        case std::ios_base::end:
            off = m_dev->size() - off;
            break;
        case std::ios_base::cur:
            off = m_dev->pos() + off;
            break;
    }
    if(m_dev->seek(off))
        return m_dev->pos();
    else
        return std::streambuf::pos_type(std::streambuf::off_type(-1));
}
virtual std::streambuf::pos_type seekpos(std::streambuf::pos_type off, std::ios_base::openmode /*__mode*/)
{
    if(m_dev->seek(off))
        return m_dev->pos();
    else
        return std::streambuf::pos_type(std::streambuf::off_type(-1));
}

virtual std::streambuf::int_type underflow()
{ 
    // Read enough bytes to fill the buffer.
    std::streamsize len = sgetn(m_inbuf, sizeof(m_inbuf)/sizeof(m_inbuf[0]));

    // Since the input buffer content is now valid (or is new)
    // the get pointer should be initialized (or reset).
    setg(m_inbuf, m_inbuf, m_inbuf + len);

    // If nothing was read, then the end is here.
    if(len == 0)
        return traits_type::eof();

    // Return the first character.
    return traits_type::not_eof(m_inbuf[0]);
}


private:
    static const std::streamsize BUFFER_SIZE = 1024;
    std::streambuf::char_type m_inbuf[BUFFER_SIZE];
    QIODevice *m_dev;
};

class QStdIStream : public std::istream
{
public:
    QStdIStream(QIODevice *dev) : std::istream(m_buf = new QStdStreamBuf(dev)) {}
    virtual ~QStdIStream()
    {
        rdbuf(0);
        delete m_buf;
    }

private:
    QStdStreamBuf * m_buf;
};

I works fine for reading local files. I haven't tested it for writing files. This code is surely not perfect but it works.

like image 37
Andreas Roth Avatar answered Oct 20 '22 03:10

Andreas Roth