Don’t be sub-class when subclassing


Let’s get started by looking at some code:

namespace MyProject {
    /*!
      \brief QLabel for MyProject

      This class is a drop-in replacement for QLabel.
      We need to subclass all widgets we use, because
      QStyle doesn't let us do all the things we want to do.
      See \link widget_subclassing Widget Subclassing for Display Classes\endlink
      for more information.
    */
    class Label : public QLabel {
    public:
        /*!
          Constructor. \a parent is passed to the base class constructor.
        */
        Label( QWidget * parent ) : QLabel( parent ) {}
    protected:
        /*! \reimp */ void paintEvent( QPaintEvent * );
    };
} // namespace MyProject

A simple enough class. It works. Well, technically. What’s to fix? Assume someone presented this class to you during code review. What would you change? And why?

How to properly subclass QObjects and not leave your fellow programmers tearing their hair out

Let’s first think about the intent of the class. Not much thinking required, it says it right there:

      This class is a drop-in replacement for QLabel.

Second: is the class living up to that intend?

Let’s tackle the issues in turn.

Missing Q_OBJECT macro

That was an easy one. We’ve all run into that one before, now haven’t we? Signals or slots that don’t exist, the works.

Yet, you can still see the old recommendation floating around that says you should only put the Q_OBJECT macro into the class definition when you have

  1. signals,
  2. slots,
  3. or properties (in the form of Q_PROPERTYs).

Guideline: When subclassing QObject (directly or indirectly), prefer to add the Q_OBJECT macro, regardless of whether you also define signals or slots.

    class Label : public QLabel {
        Q_OBJECT
    public:

There is one (rare) situation where you can’t put the Q_OBJECT macro in, and that is when your subclass is a class template. The reason is that moc just doesn’t support class templates (there’s a trick to make it do that, but it has severe limitations).

The Q_OBJECT macro does not only make the signals and slots work that you define on the class (in the introductory example, there are no signals and no slots defined on MyProject::Label). It gives a QObject subclass its identity. Without it, to Qt, MyProject::Label is just a QLabel. Quite literally: If you were to ask an instance of MyProject::Label for its class name (using metaObject()->className()), it would actually return "QLabel". We can’t be having this.

Strictly speaking, if the intent of the class was to offer a drop-in replacement for QLabel, then arguably that’s exactly what the class author did, at least as far as the meta information is concerned. However, once you introduce your own subclasses, you just might want to check whether a given widget is of your class. So, some poor maintenance fellow would try code like this:

if ( label->isA( "MyProject::Label" ) )
    // do something
else
    // do something else

Just to see it take the else part all the time. There goes one tuft of hair…

Of course, he should have used qobject_cast<MyProject::Label>( label ), which would have pointed out the problem (see the respective Effective Qt item). But if he cannot change MyProject::Label himself, he’d be stuck waiting for someone who can to fix the problem for him.

There’s another, more subtle reason for always adding the macro: Backwards compatibility, binary or otherwise. Let’s say we had collected all the MyProject widgets in a library, and that library were to maintain binary compatibility across releases. Now, adding a slot or a signal is usually backwards compatible (binary or otherwise)—they’re just normal member functions, after all—but only if the class to which you add them already had a Q_OBJECT macro.

Think about it: Q_OBJECT adds a public static QMetaObject staticMetaObject member, which subclass meta objects use as their QMetaObject::superClass(). The moc utility creates this connection from a QObject subclass to its super class at compile time. Now, if someone inherits a CoolLabel from MyProject::Label, then CoolLabel::staticMetaObject.superClass() will be &QLabel::staticMetaObject. And it will stay that way, even if MyProject::Label gets its Q_OBJECT macro added, unless you re-run moc for CoolLabel, and recompile.

In other words: Unless recompiled, MyProject::Label, when used as a base class for CoolLabel, will not be able to find its own (new) signals and slots (outside its own constructors and destructors), because MyProject::Label::staticMetaObject, even though present in MyProject::Label now, is still skipped in the chain from CoolLabel::staticMetaObject down to QLabel::staticMetaObject.

Ugh.

Missing constructors / constructor arguments

One sure sign that you’re getting comfortable with a large class library is when you start to use its classes without first having to look at their documentation. QLabel is probably one of the first Qt classes that you will know “by heart”. So it’s even more disconcerting that this “drop-in replacement” of ours does not have the constructors that we have come to love on our labels:

    explicit QLabel( QWidget * parent=0, Qt::WindowFlags f=0 );
    explicit QLabel( const QString & text, QWidget * parent=0, Qt::WindowFlags f=0 );

(we note in passing that the MyProject::Label constructor should have been explicit, too).

Guideline: Prefer to offer all base class constructors in subclasses, too.

    class Label : public QLabel {
        Q_OBJECT
    public:
        explicit Label( QWidget * parent=0, Qt::WindowFlags f=0 )
            : QLabel( parent, f ) {}
        explicit Label( const QString & text, QWidget * parent=0, Qt::WindowFlags f=0 )
            : QLabel( text, parent, f ) {}
    protected:

Even if it’s tedious work sometimes (e.g., when inheriting QTreeWidgetItem): prefer to offer all the constructors of your base class in your subclass, too. This will make it easier for users of your class to leverage their knowledge of the base class when using your subclass.

Guideline: Prefer to offer the basic QObject constructor ( QObject * parent=0 ) when subclassing QObjects. Always offer the basic QWidget constructor ( QWidget * parent=0, Qt::WindowFlags f=0 ) when subclassing QWidget.

In addition to helping your users “get it”, the basic QWidget constructor is required by uic, both for widgets that you make available through a Designer plug-in, as well as for those that you promote from a known base class in Designer itself.

Ok, ok. That’s only half the truth. What uic requires is a constructor that can be called with one QWidget* argument. It doesn’t require the window flags. And indeed, a lot of QWidget subclasses (in Qt itself) don’t have the window flags. If there is a pattern here, I have not found it yet. If in doubt, follow the previous guideline, and do as your base class does.

Additional constructor arguments

Even though it does not pertain to our initial example as given, let’s hear one more guideline, because it fits here.

Let’s assume we want to add a new property Qt::Orientation orientation to our MyProject::Label, and users of the label should be able to set the orientation at construction time. How would you change the class’ constructors? Which ones would you add?

Guideline: Prefer to follow the Qt constructor pattern: When inheriting QObjects, end constructor argument lists in QObject * parent=0. When inheriting QWidgets, end constructor argument lists in QWidget * parent=0, Qt::WindowFlags f=0. Add additional arguments at the beginning of the argument list.

    public:
        explicit Label( QWidget * parent=0, Qt::WindowFlags f=0 )
            : QLabel( parent, f ), m_orientation( Qt::Horizontal ) {}
        explicit Label( const QString & text, QWidget * parent=0, Qt::WindowFlags f=0 )
            : QLabel( text, parent, f ), m_orientation( Qt::Horizontal ) {}
        explicit Label( Qt::Orientation o, QWidget * parent=0, Qt::WindowFlags f=0 )
            : QLabel( parent, f ), m_orientation( o ) {}
        explicit Label( Qt::Orientation o, const QString & text, QWidget * parent=0, Qt::WindowFlags f=0 )
            : QLabel( text, parent, f ), m_orientation( o ) {}
        // ...
    private:
        Qt::Orientation m_orientation;
    };

(You don’t have to mark the last constructor above as explicit, but I haven’t heard of a compiler that even so much as warns about unnecessary explicits, so it doesn’t hurt to make every constructor explicit. Indeed, it makes the code more robust in the face of changes, as adding a default argument, or removing arguments, doesn’t force you to re-evaluate the decision to make a constructor explicit).

Advertisement

5 Responses to Don’t be sub-class when subclassing

  1. explicit Label( Qt::Orientation o, const QString & text, QWidget * parent=0, Qt::WindowFlags f=0 )

    This doesn’t need to be explicit, does it?

    The binary compatibility argument of adding Q_OBJECT is interesting. I would say that it is binary compatible though. Any code compiled against the pre-Q_OBJECT library will still behave as before, calling the reimplemented QObject virtuals in QLabel and using QLabel::staticMetaObject.

    Recompiling against the after-Q_OBJECT version just allows the code to use the bugfix of those reimplementations.

    • marcmutz says:

      Hi Steve,

      thanks for your comments. You’re right on both accounts, of course, even though the question of what consists Binary Compatibility is somewhat open. Even if it doesn’t crash, it doesn’t have to be binary compatible in my book, but I don’t know where exactly to draw the line, either.

      I’ve incorporated your comments into the article.

      Once again,
      Thanks,
      Marc

  2. Camila San says:

    very good article. it makes clear some points for me…
    good tips.
    thanks.

  3. Ricky says:

    Thanks for this post. I’m a an amateur c++ coder and am brand new to Qt, and I’ve been trying for a long time to find good information on best practices for subclassing different Qt classes. This is the first thing I’ve read that actually provides some of that information I’m looking for.

    I have seen a lot of people say (on stackoverflow, for example) that composition should be preferred over inheritance, especially for classes that don’t have virtual destructors (I understand that this probably isn’t an issue when you are inheriting from QObject, since the memory is managed).

    Assuming this (composition>inheritance) is true, how does this idea apply to your example above? If it isn’t true, could you explain why? If it depends on the use case, what guidelines should I keep in mind when writing my classes to determine whether to subclass Qt class X or to include it as a member?

    At the moment I am trying to write a GUI program to create and convert finite element mesh data into an input file for a specific structural analysis program. I am trying to decide whether it makes sense to subclass QPointF and QPolygonF into my Node and Element (Quad, Triangle, Beam, etc etc) classes, or whether I should use composition, or whether I should create my own classes from scratch.

    Sorry for the long question. I’m just trying to get a good start on my first real, actual project that might get used by someone.

  4. cyberdocsite says:

    Hi,
    Guess I’m a new comer to QT. Studied XCode Obj C/Swift for a while prior. Having trouble subclassing a QLabel from QT Designer or Creator in that get like a dozen compiler errors. Created my derived base QLabel class .h and .cpp files extending QLabel with just bare skeleton code for testing. Then throw a QLabel on to my dialog form and name it ‘mylabel’ and promote it to class name ‘Label’. After this step I get loads of errors like ‘error: ‘*’ before ‘;’ ‘ and others in the ui_formname.h file. Not sure what I’m missing. I’ve compiled other apps without error and if I manually add the QLabel extended class I don’t have issues. Just want to use QT Designer so I can see the layout.

    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: