109

My GUI project in Qt has a lot of "configuration pages" classes which all inherit directly from QWidget.

Recently, I realized that all these classes share 2 commons slots (loadSettings() and saveSettings()).

Regarding this, I have two questions:

  • Does it make sense to write a intermediate base abstract class (lets name it BaseConfigurationPage) with these two slots as virtual pure methods ? (Every possible configuration page will always have these two methods, so I would say "yes")
  • Before I do the heavy change in my code (if I have to) : does Qt support virtual pure slots ? Is there anything I should be aware of ?

Here is a code example describing everything:

class BaseConfigurationPage : public QWidget
{
  // Some constructor and other methods, irrelevant here.

  public slots:

    virtual void loadSettings() = 0;
    virtual void saveSettings() = 0;
};

class GeneralConfigurationPage : public BaseConfigurationPage
{
  // Some constructor and other methods, irrelevant here.

  public slots:

    void loadSettings();
    void saveSettings();
};
ereOn
  • 53,676
  • 39
  • 161
  • 238

3 Answers3

176

Yes, just like regular c++ pure virtual methods. The code generated by MOC does call the pure virtual slots, but that's ok since the base class can't be instantiated anyway...

Again, just like regular c++ pure virtual methods, the class cannot be instantiated until the methods are given an implementation.

One thing: in the subclass, you actuallly don't need to mark the overriden methods as slots. First, they're already implemented as slots in the base class. Second, you're just creating more work for the MOC and compiler since you're adding a (tiny) bit more code. Trivial, but whatever.

So, go for it..

llllllllll
  • 16,169
  • 4
  • 31
  • 54
ianmac45
  • 2,258
  • 2
  • 19
  • 15
  • 1
    Thanks for your precise answer ! I'll test this as soon as possible ;) – ereOn Jun 08 '10 at 14:47
  • 3
    Removing the slot specification from the subclass prevents moc from calling subclass AND base class! - Thanks man! – fmuecke Nov 28 '12 at 20:31
  • In Qt 5, at least, if you're using the `obj-ptr, member-func-ptr, obj-ptr, member-func-ptr` version of `connect`, *none* of your slots need to be *declared* as such. – Kyle Strand Jul 13 '16 at 17:01
  • have to add some really weird behavior: when you mark the overriden methods as slots in the header of the subclass, slots get called all the time even with 0 connections to them. Go figure!!! – bardao Sep 12 '18 at 00:14
  • In my experience (Qt6) you absolutely must not mark the overriden methods as slots. A little test example I've written with a Q_PROPERTY and virtual slots produced a "TypeError: Cannot assign to read-only property" error whenever I've marked the accessor methods in the derived class as "slots". – Vinci Jan 25 '22 at 08:56
2

Only slots in the BaseConfigurationPage

class BaseConfigurationPage : public QWidget
{
  // Some constructor and other methods, irrelevant here.

  public slots:

    virtual void loadSettings() = 0;
    virtual void saveSettings() = 0;
};

class GeneralConfigurationPage : public BaseConfigurationPage
{
  // Some constructor and other methods, irrelevant here.

    void loadSettings();
    void saveSettings();
};
lygstate
  • 564
  • 6
  • 12
  • I'm doing the same but I get 'slot name' overrides a member function but is not marked 'override' – SPlatten Nov 05 '20 at 19:51
  • Yes, `loadSettings` and `saveSettings` should be declared with `override` in `GeneralConfigurationPage` to ensure they actually override something. – Parker Coates Jun 10 '21 at 17:34
0

Others have explained the mechanics of virtuals, inheritance and slots, but I thought I'd come back to this part or question:

Does it make sense to write a intermediate base abstract class ... with these two slots as virtual pure methods ?

I would say that that only makes sense if you have a use for that abstraction, or in other words, if you have code that operates on one or more BaseConfigurationPages without caring about the actual type.

Let's say your dialog code is very flexible and holds a std::vector<BaseConfigurationPage*> m_pages. Your loading code could then look like the following. In this case, the abstract base class would make sense.

void MyWizard::loadSettings()
{
    for(auto * page : m_pages)
    {
        page->loadSettings();
    }
}

But, on the other hand, let's say that your dialog is actually pretty static and has IntroPage * m_introPage; CustomerPage * m_customerPage; ProductPage * m_productPage;. Your loading code could then look like the following.

void MyWizard::loadSettings()
{
    m_introPage->loadSettings();
    m_customerPage->loadSettings();
    m_productPage->loadSettings();
}

In this scenario, BaseConfigurationPage gains you absolutely nothing. It adds complexity and adds lines of code, but adds no expressive power and doesn't guarantee correctness.

Without more context, neither option is necessarily better.

As students or new programmers we are typically taught to identify and abstract away repetition, but that's really a simplification. We should be looking for valuable abstractions. Repetition may hint at a need for abstraction or it may just be a sign that sometimes implementations have patterns. And introducing an abstraction just because a pattern is noticed is a pretty common design trap to fall into.

The design of Dolphin and the design of Shark look a lot alike. One might be tempted to insert a TorpedoShapedSwimmer base class to capture those commonalities, but does that abstraction provide value or might it actually add unnecessary friction when it later comes time to implement breathe(), 'lactate()orgrowSkeleton()`?

I realise this is a long rant about a sub-question based on some simple example code, but I've recently run into this pattern several times at work: baseclasses that only capture repetition without adding value, but that get in the way of future changes.

Parker Coates
  • 8,520
  • 3
  • 31
  • 37