Qt 支持虚拟纯插槽吗?

我在 Qt中的 GUI 项目有很多“配置页面”类,它们都直接从 QWidget继承而来。

最近,我意识到所有这些类共享2个公共槽(loadSettings()saveSettings())。

关于这一点,我有两个问题:

  • 使用这两个插槽作为虚拟纯方法编写一个中间基抽象类(让它命名为 BaseConfigurationPage)有意义吗?(每个可能的配置页面 永远都有这两种方法,所以我会说“ yes”)
  • 在对代码进行重大修改之前(如果必须的话) : Qt 支持虚拟纯插槽吗?有什么我需要注意的吗?

下面是一个描述所有内容的代码示例:

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();
};
33715 次浏览

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

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

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.