扩展共同基础:Diamond继承与QObject

Kam*_*jii 9 c++ inheritance qt qwidget qt-signals

我想我在这里遇到了一种钻石继承问题.

Qt提供了几个旋转框,用于整数值,用于双精度以及日期/时间.它们都来自QAbstractSpinBox:

#include <QtWidgets/QSpinBox>
class QSpinBox:
    public QAbstractSpinBox {

};

#include <QtWidgets/QDoubleSpinBox>
class QDoubleSpinBox:
    public QAbstractSpinBox {

};
Run Code Online (Sandbox Code Playgroud)

现在我想添加一些通用于所有旋转框的功能,在这个具体的例子中,一个按钮将旋转框恢复到最小(因此是specialValueText).所以我也从中衍生出来QAbstractSpinBox并想出了这样的东西:

class AbstractRevertibleSpinBox:
    public QAbstractSpinBox {

    public:
        RevertibleSpinBox() {
            /* Use abstract base: */
            QAction *revertAction = new QAction(this);
            QAbstractSpinBox::lineEdit()->addAction(
                revertAction, QLineEdit::TrailingAction);
            /* ... */
        }

    public slots:
        virtual void revert()  = 0;
}
Run Code Online (Sandbox Code Playgroud)

revert()包含应该实现如何恢复不同旋转框的纯净.例如,setValue(double)用于QDoubleSpinBoxsetDate(QDate)用于QDateEdit.然后我以明显的方式为我需要的所有旋转框派生了相应的类,如下所示:

class RevertibleSpinBox:
    public QSpinBox,
    public AbstractRevertibleSpinBox {

    protected:
        void revert() {
            /* Revert 'int' */
            setValue(0);
        }
};

class RevertibleDoubleSpinBox:
    public QDoubleSpinBox,
    public AbstractRevertibleSpinBox {

    protected:
        void revert() {
            /* Revert 'double' */
            setValue(0.0);
        }
};
Run Code Online (Sandbox Code Playgroud)

这显然不起作用,因为QAbstractSpinBox中的任何内容现在都不明确.我以为我可以使用虚拟继承解决它,如果,例如,这将工作QDoubleSpinBox几乎从自身派生QAbstractSpinBox.但事实并非如此.此外,它会因QObject而失败,因为Qt似乎在static_cast那里做了很多挫折,这也不适用于虚拟继承.我还考虑通过将AbstractRevertibleSpinBox传递独特的旋转框类型的模板类作为模板类参数来解析它.然后构造将如下所示:

template<class Base>
class AbstractRevertibleSpinBox:
    public Base {};

class RevertibleSpinBox:
    public AbstractRevertibleSpinBox<SpinBox> { };
Run Code Online (Sandbox Code Playgroud)

这可行,但Qt​​的moc对模板类非常不满意.因此,例如,我无法连接模板类中的任何信号和插槽.至少不使用传统的基于字符串的SIGNAL()/SLOT()语法.

有没有其他合理优雅的方法来克服这个问题..?

lpa*_*app 7

正如我在评论中所指出的那样,如果你想要一个易于扩展的特征系统,我认为这是装饰器模式的一个明显的例子,否则只是从QObject继承,而不是从基本的"接口"继承几乎相同的代码.

我将从恕我直言的更糟糕的方法开始,在其他答案中提供:

  • 对每个旋转框进行子类化

这显然是令人厌烦的,甚至更重要的是,您将无法支持任何QSpinBox子类,因为您总是需要为每个添加创建一个新的子类.这只是一种不灵活的方法.

  • 拥有包含按钮和旋转框的父窗口小部件

这看起来像是两个不同事物的不必要的耦合,因此如果您通过按钮之后以任何其他方式触发它们,您将无法轻松地重复使用旋转框.我认为这两个概念应该保持不同并单独管理.

此外,dynamic_cast如你所知,你应该使用ing是错误的qobject_cast.

让我们仔细看看装饰器的方法:

在此输入图像描述

这还不是您的案例的解决方案,但它很好地展示了如何将功能添加(即"装饰")到现有层次结构中.为了更加具体地了解您的用例,让我们看看您的特定场景中会出现什么:

  • 组件:QAbstractSpinBox

  • 具体组件

    • QSpinBox
    • QDoubleSpinBox
    • QDateTimeEdit
      • QDateEdit
      • QTimeEdit
  • 装饰者:AbstractSpinBoxDecorator(在你的情况下可以省略这一步)

  • 具体装饰:RevertibleSpinBoxDecorator

让我们实现这个设计:

main.cpp中

#include <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QDateTimeEdit>
#include <QDateEdit>
#include <QTimeEdit>
#include <QPushButton>
#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QWidget>
#include <QShowEvent>

class RevertibleSpinBoxDecorator : public QAbstractSpinBox
{
    Q_OBJECT
public:
    explicit RevertibleSpinBoxDecorator(QAbstractSpinBox *abstractSpinBox, QAbstractSpinBox *parent = Q_NULLPTR)
        : QAbstractSpinBox(parent)
        , m_abstractSpinBox(abstractSpinBox)
    {
    }

public slots:
    void revert(bool)
    {
        QSpinBox *spinBox = qobject_cast<QSpinBox*>(m_abstractSpinBox);
        if (spinBox) {
            spinBox->setValue(spinBox->minimum());
            return;
        }

        QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(m_abstractSpinBox);
        if (doubleSpinBox) {
            doubleSpinBox->setValue(doubleSpinBox->minimum());
            return;
        }

        QDateEdit *dateEdit = qobject_cast<QDateEdit*>(m_abstractSpinBox);
        if (dateEdit) {
            dateEdit->setDate(dateEdit->minimumDate());
            return;
        }

        QTimeEdit *timeEdit = qobject_cast<QTimeEdit*>(m_abstractSpinBox);
        if (timeEdit) {
            timeEdit->setTime(timeEdit->minimumTime());
            return;
        }

        QDateTimeEdit *dateTimeEdit = qobject_cast<QDateTimeEdit*>(m_abstractSpinBox);
        if (dateTimeEdit) {
            dateTimeEdit->setDateTime(dateTimeEdit->minimumDateTime());
            return;
        }

        Q_ASSERT_X(false, "decorator", "concrete component unimplemented");
    }

protected:
    void showEvent(QShowEvent *event) Q_DECL_OVERRIDE
    {
        m_abstractSpinBox->show();
        event->ignore();
        hide();
    }

private:
     QAbstractSpinBox *m_abstractSpinBox;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
    public:
        explicit MainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
        {
            connect(pushButton, &QPushButton::clicked, revertibleSpinBoxDecorator, &RevertibleSpinBoxDecorator::revert);
            QHBoxLayout *layout = new QHBoxLayout(centralWidget);
            layout->addWidget(revertibleSpinBoxDecorator);
            layout->addWidget(pushButton);
            setCentralWidget(centralWidget);
        }

    private:
        QWidget *centralWidget{new QWidget(this)};
        QDoubleSpinBox *doubleSpinBox{new QDoubleSpinBox(this)};
        RevertibleSpinBoxDecorator *revertibleSpinBoxDecorator{new RevertibleSpinBoxDecorator(doubleSpinBox)};
        QPushButton *pushButton{new QPushButton(this)};
};

#include "main.moc"

int main(int argc, char **argv)
{
    QApplication application(argc, argv);
    MainWindow mainWindow;
    mainWindow.show();
    return application.exec();
}
Run Code Online (Sandbox Code Playgroud)

如果你想摆脱QAbstractSpinBox继承,你将需要更多的胶水和恕我直言,以获得不多的收益,同时失去灵活性.你会从这样的事情开始:

非装饰

#include <QAbstractSpinBox>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QDateTimeEdit>
#include <QDateEdit>
#include <QTimeEdit>
#include <QPushButton>
#include <QApplication>
#include <QMainWindow>
#include <QHBoxLayout>
#include <QWidget>
#include <QShowEvent>

class RevertibleSpinBoxDecorator : public QObject
{
    Q_OBJECT
public:
    explicit RevertibleSpinBoxDecorator(QAbstractSpinBox *abstractSpinBox, QObject *parent = Q_NULLPTR)
        : QObject(parent)
        , m_abstractSpinBox(abstractSpinBox)
    {
    }

public slots:
    void revert(bool)
    {
        QSpinBox *spinBox = qobject_cast<QSpinBox*>(m_abstractSpinBox);
        if (spinBox) {
            spinBox->setValue(spinBox->minimum());
            return;
        }

        QDoubleSpinBox *doubleSpinBox = qobject_cast<QDoubleSpinBox*>(m_abstractSpinBox);
        if (doubleSpinBox) {
            doubleSpinBox->setValue(doubleSpinBox->minimum());
            return;
        }

        QDateEdit *dateEdit = qobject_cast<QDateEdit*>(m_abstractSpinBox);
        if (dateEdit) {
            dateEdit->setDate(dateEdit->minimumDate());
            return;
        }

        QTimeEdit *timeEdit = qobject_cast<QTimeEdit*>(m_abstractSpinBox);
        if (timeEdit) {
            timeEdit->setTime(timeEdit->minimumTime());
            return;
        }

        QDateTimeEdit *dateTimeEdit = qobject_cast<QDateTimeEdit*>(m_abstractSpinBox);
        if (dateTimeEdit) {
            dateTimeEdit->setDateTime(dateTimeEdit->minimumDateTime());
            return;
        }

        Q_ASSERT_X(false, "strategy", "strategy not implemented");
    }

private:
     QAbstractSpinBox *m_abstractSpinBox;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
    public:
        explicit MainWindow(QWidget *parent = Q_NULLPTR) : QMainWindow(parent)
        {
            connect(pushButton, &QPushButton::clicked, revertibleSpinBoxDecorator, &RevertibleSpinBoxDecorator::revert);
            QHBoxLayout *layout = new QHBoxLayout(centralWidget);
            layout->addWidget(doubleSpinBox);
            layout->addWidget(pushButton);
            setCentralWidget(centralWidget);
        }

    private:
        QWidget *centralWidget{new QWidget(this)};
        QDoubleSpinBox *doubleSpinBox{new QDoubleSpinBox(this)};
        RevertibleSpinBoxDecorator *revertibleSpinBoxDecorator{new RevertibleSpinBoxDecorator(doubleSpinBox)};
        QPushButton *pushButton{new QPushButton(this)};
};

#include "main.moc"

int main(int argc, char **argv)
{
    QApplication application(argc, argv);
    MainWindow mainWindow;
    mainWindow.show();
    return application.exec();
}
Run Code Online (Sandbox Code Playgroud)

main.pro

TEMPLATE = app
TARGET = main
QT += widgets
CONIG += c++11
SOURCES += main.cpp
Run Code Online (Sandbox Code Playgroud)

构建并运行

qmake && make && ./main
Run Code Online (Sandbox Code Playgroud)