Does Qt support virtual pure slots?
Asked Answered
F

3

110

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();
};
Fleischer answered 8/6, 2010 at 14:27 Comment(0)
M
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..

Moujik answered 8/6, 2010 at 14:43 Comment(5)
Thanks for your precise answer ! I'll test this as soon as possible ;)Fleischer
Removing the slot specification from the subclass prevents moc from calling subclass AND base class! - Thanks man!Pinion
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.Alvinia
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!!!Chopfallen
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".Industrialism
H
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();
};
Hankow answered 22/11, 2013 at 8:20 Comment(2)
I'm doing the same but I get 'slot name' overrides a member function but is not marked 'override'Raffin
Yes, loadSettings and saveSettings should be declared with override in GeneralConfigurationPage to ensure they actually override something.Ecbolic
E
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.

Ecbolic answered 11/6, 2021 at 12:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.