When you define your own C++ types for a Qt application, you want to print their values with qDebug()
, qWarning()
or qCritical()
eventually.
auto frame1 = QCanBusFrame{0x18ef0201U, QByteArray::fromHex("018A010000000000")};
qDebug() << "frame1 =" << frame1;
The C++ compiler will complain with this error message:
error: no match for ‘operator<<’ (operand types are ‘QDebug’ and ‘QCanBusFrame’)
The error message tells you to write your own output operator for the QDebug
stream and for QCanBusFrame
.
A custom C++ type that wants to play nicely with Qt should always define a QDebug
output operator. Even the developers of the Qt library seem to forget it sometimes, as the example of QCanBusFrame
shows.
The documentation of QDebug Class gives some hints about writing custom types to QDebug
streams and links to the sections Providing Support for the qDebug() Stream Operator and Making the Type Printable. The first implementation of the stream operator for QCanBusFrame
is a simple adaptation of the example given in the documentation of QDebug Class.
QDebug operator<<(QDebug debug, const QCanBusFrame &frame)
{
QDebugStateSaver saver(debug);
debug.nospace() << frame.toString();
return debug;
}
The line
qDebug() << "frame1 =" << frame1;
prints
frame1 = "18EF0201 [8] 01 8A 01 00 00 00 00 00"
The line QDebugStateSaver saver(debug);
saves the current state of the stream and restores this state when exiting the operator function. Hence, formatting changes by the custom operator don’t affect any other calls of operator<<
. They stay local to the custom operator.
The QDebug
stream has a special thing with spaces and with quotes. It inserts a space between each argument and encloses QString
, QByteArray
and QChar
arguments with double quotes. With the inserted space replaced by “@”, the last output looks as follows:
frame1 =@"18EF0201 [8] 01 8A 01 00 00 00 00 00"
As "frame1 ="
has the type const char *
, it is printed without quotes. The CAN frame, however, is enclosed with quotes, because frame.toString()
has the type QString
.
The formatting option debug.nospace()
ensures that the stream operator for QCanBusFrames does not insert its own space between the equality sign and the opening quote. If you wrote
debug.space() << frame.toString();
in the custom operator, the output (again with “@” instead of space) would be
frame1 =@@"18EF0201 [8] 01 8A 01 00 00 00 00 00"
If you wrote
debug.maybeSpace() << frame.toString();
the custom operator would insert a space or not depending on the preceding formatting options in the QDebug
stream.
qDebug() << "frame1 =" << frame1;
# frame1 =@@"18EF0201 [8] 01 8A 01 00 00 00 00 00"
qDebug().nospace() << "frame1 =" << frame1;
# frame1 ="18EF0201 [8] 01 8A 01 00 00 00 00 00"
qDebug().nospace()
disables the automatic insertion of spaces and prevents debug.maybeSpace()
from inserting a space. By default, the insertion of spaces is enabled and debug.maybeSpace()
inserts a space.
The output format of QCanBusFrame::toString()
isn’t ideal, because you cannot pass it to Linux CAN utilities like cansend
and cangen
. For example,
$ cansend can0 18EF0201#018A010000000000
writes frame1
to the CAN interface can0
from the Linux command line. The following implementation of operator<<
produces the desired output format.
QDebug operator<<(QDebug debug, const QCanBusFrame &frame)
{
QDebugStateSaver saver(debug);
debug.nospace().noquote()
<< QByteArray::number(frame.frameId(), 16) << "#"
<< frame.payload().toHex();
return debug;
}
The option noquote()
is required to suppress the quotes around the first and third argument, which are both QByteArray
s.
The other message loggers qInfo()
, qWarning()
, qCritical()
and qFatal()
work with the custom type QCanBusFrame
as well, because they all write to the QDebug
stream.