Diamond Inheritance Lowest Base Class Constructor

Diamond Inheritance Lowest Base Class Constructor

The most derived class initializes any virtual base classes. In your class hierarchy, Unknown must construct the virtual Animal base class (e.g. by adding Animal(a) to its initialization list).

When constructing an Unknown object, neither Fish nor Bird will call the Animal constructor. Unknown will call the constructor for the Animal virtual base.

C++ base class function call from last derived in diamond design

Your question seems to not be related to the diamond pattern, but is generally about the inheritance model in C++. Most things in C++ are statically bound, so at compile time the compiler fixes what method or member of a certain name is used:

If you access a member of the implicit this object or using a pointer or reference to an object, you will end up accessing a member of the class the pointer or reference has at compile time, no matter whether at runtime there is a derived-class object that has members of the same name. That's why they say you can not override but just shadow members in base classes when you define an equal named member in the derived class.

The same thing is true for non-virtual functions.

The only thing that behaves different is virtual functions. These functions can be overwritten in derived classes, and code that gets handed a pointer-to-base-class can virtual functions of the base class and end up executing implementations given in the derived class.

So the point of virtual functions is not to have the compiler reinterpret a certain function in the context of derived classes (as you seem to understand it), but to make it possible to replace a function in the base class by a different function in the derived class.

To go back to your motivation: If you want a display function that prints a message that depends on the actual object type, the thing that is fixed is the printig, which doesn't need to be virtual. But you need a virtual function to obtain the object type. Like this:

#include <iostream>
#include <ostream>

class A {
public:
void display() { std::cout << get_message() << '\n'; }
virtual const char * get_message() { return "A instance"; }
};

class B : virtual public A {
public:
virtual const char * get_message() { return "B instance"; }
};

class C : virtual public A {
public:
virtual const char * get_message() { return "C instance"; }
};

class D : public B, public C {
public:
// virtual const char * get_message() { return B::get_message(); }
// virtual const char * get_message() { return C::get_message(); }
// virtual const char * get_message() { return "D instance"; }
};

int main(void)
{
D foo;
foo.display();
A* a_ptr = &foo;
a_ptr->display();
return 0;
}

The example as given will not compile (now this is due to the diamond pattern), because the compiler can not decide, what overrider of A::get_message(), either B::get_message() or C::get_message() should be picked in D objects, you need to make one of the comments in D real code to declare the one-and-only get_message for D, in which can re-use the existing functions.

Why can a derived class call base class constructor twice?

Drawing from link is misleading, it is not a diamond, but more a Y:

 ----------    ----------
| Person | | Person |
---------- ----------
^ ^
| |
---------- ----------
| Student | | Faculty |
---------- ----------
^ ^
| |
\----- ----/
\ /
|
----------
| TA |
----------

And yes you have two copies of member of Person (Student::y and Faculty::y).

With virtual inheritance, you have a diamond with only one unique Person.

Inheriting from multiple/diamond Inheritance

You need to make all your constructors public and define a default constructor for A because the string constructor will mark the default constructor as =delete. Furthermore, the most derived class will initialize any virtual base class, quoting from the draft Standard:

12.6.2 Initializing bases and members [class.base.init]

10 In a non-delegating constructor, initialization proceeds in the
following order: — First, and only for the constructor of the most
derived class (1.8), virtual base classes are initialized in the order
they appear on a depth-first left-to-right traversal of the directed
acyclic graph of base classes, where “left-to-right” is the order of
appearance of the base classes in the derived class
base-specifier-list.

In this case that means that X must indeed initalize A.

#include <iostream>
#include <string>

class A
{
public:
A() { std::cout << "A\n"; }
A(std::string id) { std::cout << id << " A(id)\n"; }
};

class B : public virtual A
{
public:
B() { std::cout << "B\n"; }
};

class C : public virtual A
{
public:
C() { std::cout << "C\n"; }
};

class D : public B, public C
{
public:
D(std::string id): A(id) { std::cout << id << " D(id)\n"; }
};

class X : public D
{
public:
X(std::string id): A(id), D(id) { std::cout << id << " X(id)\n"; }
};

int main()
{
X x("bla");
x;
}

Extending a common base: Diamond inheritance vs. QObject

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:

Sample Image

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


Related Topics



Leave a reply



Submit