Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending a common base: Diamond inheritance vs. QObject

I think I've run into a kind of diamond inheritance problem here.

Qt provides a couple of spin boxes, for integer values, for doubles and also for dates/times. They all derive from QAbstractSpinBox:

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

};

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

};

Now I'd like to add some functionality common to all spin boxes, in this concrete example, a button to revert the spin box to its minimum (hence specialValueText). So I also derived from QAbstractSpinBox and came up with something like this:

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

This containts the pure revert() that should implement how to revert the different spin boxes. For example, using setValue(double) for the QDoubleSpinBox or setDate(QDate) for the QDateEdit. And then I went the obvious way and derived the appropriate classes for all the spin boxes I needed, like these:

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

This obviously does not work as anything in QAbstractSpinBox is ambiguous now. I thought I could resolve it using virtual inheritance and this would work if, e.g. QDoubleSpinBox would virtually derive from its own QAbstractSpinBox. But it doesn't. Also, it would fail with QObject because Qt seems to do a lot of static_cast upwarts there, which also does not work with virtual inheritance. I also thought of resolving it by making the AbstractRevertibleSpinBox a template class that is passed the distinct spin box type as a template class parameter. The construction would then look like this:

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

class RevertibleSpinBox:
    public AbstractRevertibleSpinBox<SpinBox> { };

This would work, however Qt's moc is very unhappy about template classes. So for example, I cannot connect any signals and slots from within the template class. At least not using the conventional string based SIGNAL()/SLOT() syntax.

Is there any other reasonably elegant way to overcome this problem..?

like image 983
Kamajii Avatar asked Dec 12 '14 18:12

Kamajii


People also ask

What is diamond problem in Java with example?

The diamond problem occurs when two superclasses of a class have a common base class. For example, in the following diagram, the TA class gets two copies of all attributes of Person class, this causes ambiguities. For example, consider the following program. In the above program, constructor of ‘Person’ is called two times.

What is multiple inheritance in C++?

Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. The constructors of inherited classes are called in the same order in which they are inherited.

Why use typescript inheritance to extend components?

Whether you are handling navigation, common modal alert UI, or anything else, using the inheritance model granted via TypeScript to extend components is a powerful tool to keep in our toolbox. The code for this tutorial is available on GitHub.

How to override the functions of extending class in Java?

You can access the variables of extending (Base) class if they are not private (protected or internal or public) can override them if they are not final (open variables) or you can override the functions also. Here we added a property i.e. rollNumber and override the function.


2 Answers

As indicated right upfront in my comment, I think this is a clear case for the Decorator Pattern if you want an easily extensible feature system, otherwise just inherit from QObject rather than from the base "interface" with pretty much the same code.

I will start with the IMHO worse approaches, supplied in the other answers given:

  • Subclassing each spin box

This is obviously tiresome and even more important, you will not be able to support any QSpinBox subclass with those as you would always need to create a new subclass for each addition. It is simply an unflexible approach.

  • Have a parent widget containing the button and spin box

This looks like an unnecessary coupling of two different things, and so you would not be able to reuse spin boxes easily should you trigger them any other way later than through buttons. I think the two concepts should remain distinct and separately managed.

Furthermore, dynamic_casting is wrong as suggested as you should use qobject_cast if any.

Let us have a closer look at the decorator approach:

enter image description here

This is not yet the solution for your case, but it shows pretty well how features can be added (i.e. "decorated") into existing hierarchies. To get a bit more concrete about your use case, let us see what would be what in your particular scenario:

  • Component: QAbstractSpinBox

  • Concrete components

    • QSpinBox
    • QDoubleSpinBox
    • QDateTimeEdit
      • QDateEdit
      • QTimeEdit
  • Decorator: AbstractSpinBoxDecorator (this step can be left out in your case)

  • Concrete Decorator: RevertibleSpinBoxDecorator

Let us get our hands dirty with implementing this design:

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

If you want to get rid of the QAbstractSpinBox inheritance, you will need to apply a bit more glue and IMHO for not much gain, while losing the flexibility. You would start off with something like this:

Non-Decorator

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

main.pro

TEMPLATE = app
TARGET = main
QT += widgets
CONIG += c++11
SOURCES += main.cpp

Build and Run

qmake && make && ./main
like image 105
lpapp Avatar answered Oct 07 '22 11:10

lpapp


I don't think I would use inheritance here at all; instead I'd use composition: create an unrelated class (e.g. derived from QWidget) that uses a QBoxLayout (or similar) to arrange the spin-box and the button as child widgets. The appropriate spin-box object-pointer could be passed to the class's constructor, which would also do the necessary connect() commands to forward the various signals back and forth. You'd probably want to redeclare equivalents for the QAbstractSpinBox class's slots/signals/methods in your class, of course, but the upside is that it would work with any of the QAbstractSpinBox sub-classes.

(As for getting revert() to do the right thing, the easiest approach might be just some ugly special-case logic using dynamic_cast<> -- since there are only three QAbstractSpinBox subclasses you need to support, that will be manageable, and at least that way the ugliness is hidden inside a private method body rather than being exposed to the class's users)

like image 33
Jeremy Friesner Avatar answered Oct 07 '22 11:10

Jeremy Friesner