Home > Software engineering >  When you create a new class based on QObject, why does the wizard mark your constructor explicit?
When you create a new class based on QObject, why does the wizard mark your constructor explicit?

Time:07-10

I have never really paid attention to explicit, and I am not terribly sure what to infer from it when I find it in a class header. Looking through my code, I noticed that in QtCreator, when you create a new class based on QObject, it always marks the constructor explicit.

#ifndef SNEED_H
#define SNEED_H

#include <QObject>

class Sneed : public QObject
{
    Q_OBJECT
public:
    explicit Sneed(QObject *parent = nullptr);

signals:

};

#endif // SNEED_H

I have been programming with Qt for several years, and there has never been a time where I ran into a bug with QObject that was due to the constructor lacking the explicit keyword.

If I took out explicit, what sort of bug could I end up creating because of this?

Why does QObject specifically generate an explicit keyword via the wizard?

My guess is that is has something to do with preventing copy initialization, which QObject also does not allow, but I am not sure.

Thanks.

CodePudding user response:

It is used for single parameter constructors as that is when it makes sense.

This way, you disable implicit conversion from a type to a different so that the compiler will generate an error for you.

This makes sense in certain cases when you want to make your API user think and make the conversion explicitly.

This can catch accidental errors in the code when the user is not actually meant to get an implicit conversion.

QSlider *s = new QSlider();
Sneed *s1 = new Sneed();
Sneed *s2 = s; // Typo: wanted to copy s1. Compiler catches.

Here is a more detailed explanation for the whole topic by KDAB: https://www.youtube.com/watch?v=GUdQ9u34HQI

Here is a little more complete example:

class String
{
public:
    String(const char *); // Should be implicit
    String(int size); // Should be explicit
};
void print(const String &s);

int main()
{
    String s = "hello";
    print(s); // All good.
    print("hello"); // All good.
    String preallocated(123);
    print(preallocated); // All good.
    print(123); // Not printing 123 as intuitively expected. 
}

QObject example:

class MyWidget : public QWidget
{
public:
    MyWidget(QObject *parent = nullptr)
        : QWidget(parent)
    {}

    QString getData() const { return "some data"; }
};

MyWidget *createWidget()
{
    return new MyWidget;
}

void testWidget1()
{
    // Accidentally created on the stack. How can it compile?
    // Implicit conversion, parented rather than taking the pointer.
    MyWidget w = createWidget();
}

This can become even worse when mixing unrelated QObject (QWidget in this case) subclasses.

void testWidget2()
{
    QPushButton *w1 = new QPushButton;
    // Unrelated push button, parented, not meant it.
    MyWidget *w2 = w1;
}

Even more challenging to spot the mistake in the following example that a temporary is created.

MyWidget testWidget3()
{
    QPushButton *w1 = new QPushButton;
    // If the function is long, you will not even see the
    // return type here, just assume it is MyWidget*.
    // How come I do not see the widget despite showVisible(), etc?
    return w1;
}

Yet another example when a function or method parameter would accidentally use const reference when it should use the idiomatic Qt way, which is a pointer in this case.

void debug(const MyWidget &w) { qDebug() << w.getData(); }

void testWidget4() {
    // temporary again, no pointer.
    debug(new QPushButton);
}

Should such constructors be always explicit? No. Sometimes, it makes sense to have them implicit. For example, it would be inconvenient having to convert a const char * explicitly to a String as in the above example. The decision should go on a case-by-case basis for constructors.

Explicit can be a good default though if you are unsure as you can always open up later also to implicit conversion.

Some consider that the default C behaviour ought to be explicit rather than implicit conversion.

Either way, one would still need to understand the differences and consider the decision on a case-by-case basis.

Also, even if you want to opt out, you should consider using Q_IMPLICIT (macro expanding to nothing) in Qt 6 to mark your intention so that when anyone reviews your code in the future, they can notice that it is deliberate rather than an accidental omission of explicit.

In C 20, you can even use the conditional explicit keyword, as explicit(false) instead of Q_IMPLICIT.

clang-tidy can also check it for you whether it is clearly defined rather than left uncertain.

I would also like to mention some cases where an explicit conversion required instead of accepting implicit conversion could become inconvenient.

void drawLine(QPoint a, QPoint b);
drawLine({0, 0}, {5, 5});

If you needed to draw thousands of lines, the explicit conversion would become really cumbersome.

So, the trade-off is always convenience vs. catching accidental mistakes. In the above QPoint example, this is likely not very error-prone to accidental mistakes.

The constructor should be implicit if the source data type is a faithful representation of the target type. const char * and String pretty much represent the same.

But how an integer would map to a String can be more ambiguous. The entities represent different things. So, the constructor ought to be explicit.

  • Related