When to check for error when using QIODevice's

2019-05-13 10:14发布

问题:

For learning purposes I made application that sends file across network (which work very well for me). Here I will post main part of code, the code that actually sends bytes, I think that is enough.

My primary question is: When, where, why and how should I check for errors? (looks like more than one question :) )

As you can see, I checked for errors by checking return values of every function that can warn me that way (I marked every check with number to make easier to those who want to help to answer and explain).

Is this necessary? Because it can expand code significantly.

Secondary question: Is this what I made OK, is there a better way to do it?

while(!file->atEnd()){
   if(isCancelled())//this is in a thread, and there is mechanism to cancel it
       return;

   if((readed = file->read(inter_buffer,BUFLEN)) == -1){ //1 <- marking check with "1"
       emit errorOccurred(tr("Error while reading file."));
       return;
   }

   if(socket->write(inter_buffer,readed) == -1){//2 QTcpSocket::write
       emit errorOccurred(tr("Unable to send data. Probably the other side cancelled or there are connection problems."));
       qDebug() << socket->error();
       return;
   }

   rsofar += readed;

   if(!socket->flush()){//3
       emit errorOccurred(tr("Unable to send data. Probably the other side cancelled or there are connection problems."));
       return;
   }

   //emit signal to inform GUI thread about progress
   emit updateProgress((int)(((double)rsofar)/(double)filesize * 100.0));

   if(!socket->waitForBytesWritten()){//4
       //maybe this is not the right message, but that is not important now
      emit errorOccurred(tr("Unable to send data. Probably the other side cancelled or there are connection problems."));
       return;
   }

}

Тertiary question is: In Java I would rely on Exceptions to handle this kind of problems. Why Qt functions does not throw exceptions? Is it because it is considered slow for C++ (because of stack unrolling), or just bad habit when programming in C++, or because it does not work well with signals and slots, or something else?

回答1:

Exceptions can add memory and runtime overhead on some C++ implementations. It's not a problem on modern, well maintained C++ implementations - but Qt has to run and compile on some really obsolete or awkward platforms. Not only that - Qt (at least the core) has to compile and run properly with compiler's exception support disabled.

Your error code checking is almost correct. In your case, if write returns any size other than readed, it should be treated as an error. Grammar nitpick: the correct form is "read", not "readed". Yes, you have "written" but simply "read". English is weird like that ;)

There is no need to use flush(). Just waitForBytesWritten and then check how many bytes still remain to be written and report progress based on that. You're making things run slower since your approach can't amortize the latency of disk file access: you don't do network sending and file reading in parallel.

So, what you're doing is somewhat convoluted. You don't need to use blocking waitForX functions at all. You're running in a thread, so let's just use signals provided by QIODevice and use the default event loop that QThread's run() method is spinning. That way you can process multiple files in the same worker thread. Your implementation requires a dedicated thread for each file processed in parallel.

The code below should work. Simply use moveToThread to move it to a worker QThread - don't derive from QThread. To start sending, invoke the start() slot. To cancel sending, all you need to do is to call sender->deleteLater().

#include <QTcpSocket>
#include <QByteArray>

class Sender : public QObject {
    Q_OBJECT
    QIODevice * m_src;
    QAbstractSocket * m_dst;
    QByteArray m_buf;
    qint64 m_hasRead;
    qint64 m_hasWritten;
    qint64 m_srcSize;
    bool m_doneSignaled;
    bool signalDone()  {
        if (!m_doneSignaled &&
                ((m_srcSize && m_hasWritten == m_srcSize) || m_src->atEnd())) {
            emit done();
            m_doneSignaled = true;
        }
        return m_doneSignaled;
    }
    Q_SLOT void dstBytesWritten(qint64 len) {
        if (m_dst->bytesToWrite() < m_buf.size() / 2) {
            // the transmit buffer is running low, refill
            send();
        }
        m_hasWritten += len;
        emit progressed((m_hasWritten * 100) / m_srcSize);
        signalDone();
    }
    Q_SLOT void dstError() {
        emit errorOccurred(tr("Unable to send data. Probably the other side"
                              "cancelled or there are connection problems."));
        qDebug() << m_dst->error();
    }
    void send() {
        if (signalDone()) return;
        qint64 read = m_src->read(m_buf.data(), m_buf.size());
        if (read == -1) {
            emit errorOccurred(tr("Error while reading file."));
            return;
        }
        m_hasRead += read;
        qint64 written = m_dst->write(m_buf.constData(), read);
        if (written == -1) {
            emit errorOccurred(tr("Unable to send data. Probably the other side "
                                  "cancelled or there are connection problems."));
            qDebug() << m_dst->error();
            return;
        }
        if (written != read) {
            emit errorOccurred(tr("Internal error while filling write buffer."));
            qDebug() << m_dst->error();
            return;
        }
    }
public:
    /*! Requires a source device open for reading, and a destination socket open
        for writing. */
    Sender(QIODevice * src, QAbstractSocket * dst, QObject * parent = 0) :
        QObject(parent), m_src(src), m_dst(dst), m_buf(8192, Qt::Uninitialized),
        m_hasRead(0), m_hasWritten(0), m_doneSignaled(false)
    {
        Q_ASSERT(m_src->isReadable());
        Q_ASSERT(m_dst->isWritable());
        connect(m_dst, SIGNAL(bytesWritten(qint64)), SLOT(dstBytesWritten(qint64)));
        connect(m_dst, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(dstError()));
        m_srcSize = m_src->size();
    }
    Q_SLOT void start() { send(); }
    Q_SIGNAL void done();
    Q_SIGNAL void errorOccurred(const QString &);
    Q_SIGNAL void progressed(int percent);
};