Does Qt support virtual pure slots?

C++QtInheritanceSignals Slots

C++ Problem Overview


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();
};

C++ Solutions


Solution 1 - C++

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..

Solution 2 - C++

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();
};

Solution 3 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionereOnView Question on Stackoverflow
Solution 1 - C++ianmac45View Answer on Stackoverflow
Solution 2 - C++lygstateView Answer on Stackoverflow
Solution 3 - C++Parker CoatesView Answer on Stackoverflow