1
votes

I have a binary packet format which I must implement a C++ reader for. The library uses Qt 4, and the packet source could be any QIODevice, for example, QTcpSocket, QFile or QBuffer. The format includes the packet format, and each packet may also have a lot of sub-structures inside. I need the reader to return the following:

  • the packet header;
  • the array of sub-structures;
  • the error status of a read operation - success, an error, or not enough data (particularly when reading from a socket or another sort of buffering device).

There are various possible approaches to the reader API:

  1. Packet read(Status &status); - return by value, and return the error status through the reference argument.
  2. Packet *read(bool *ok); - return NULL on error or if not enough data, write true or false to the ok variable (if not NULL) depending on that.
  3. Packet *read(); - return NULL on error or if not enough data, call another method bool wasError(); to check what happened. This one could be merged with the previous one by making the ok parameter have the default value of NULL.
  4. Status read(Packet &packet); - if the returned status is Ok, the read value is placed into the packet variable, otherwise it indicates either an error or EOF.
  5. Packet read(); - return by value, in case of EOF or error return a special "null packet" value. Call wasError() to determine what happened.

There are other possible combinations, of course. There doesn't seem to be the best choice. The approaches 1, 2 and 4 require the caller to declare a separate variable to store the result. The approaches 2 and 3 involve messing with the heap, which I don't want to do for obvious reasons. The approach 1 doesn't make it clear what is returned in case of error. The approach 5 fixes that, but introduces a special "null" flag into the packet structure, although it probably doesn't belong there.

I could take the 5th approach, but return a special structure containing a packet and the status info, but that introduces another "synthetic" type and still leaves open the question "What will the packet field contain in case of an error?"

Or I could take the 3rd approach and return a QSharedPointer<Packet> instead, so the caller doesn't have to mess with heap manually. But the Packet structure will probably already be a kind of smart pointer (a shared class) for the sake of the Pimpl. Maybe I can use that internal pointer instead and introduce an isNull() method, like QString does.

Is there a better, or traditional way of doing it?

1
I may provide some answers, but right know I have to think through. The are no simple aswers, others depend on format, behavior, system desing.Arpegius

1 Answers

0
votes

Well, I've gone ahead and implemented it as follows.

The signature of the reading method is:

Packet readPacket(bool *error = NULL);

The Packet class looks like this:

class Packet {
    inline Packet(): p(NULL) {} // this is returned on error or EOF
    Packet(const Header &header, const Data &data);
    inline bool isNull() {return p == NULL;}
private:
    QSharedDataPointer<PacketPrivate> p;
};

The packet reader also has the bool wasError() method used to retrieve the error status in case the error argument wasn't provided to the readPacket() function.

This implementation allows me:

  1. To create elegant loops like while (!(packet = reader->readPacket()).isNull()) ...
  2. To avoid having unrelated fields like "error" or "null" in the Packet class.
  3. To avoid manual allocating or deleting pointers, or having to mess with smart pointers explicitly at the caller's side.
  4. To avoid having uninitialized fields when using default constructor.

It is still far from perfect because it is necessary to either provide the error argument or to call wasError() later. But I believe the only way of forcing the caller to check for errors would be to use exceptions, but I don't want to do it for portability reasons.