QObject, multiple inheritance, and smart delegators
Hi there,
In last weeks I've been working on a Qt-based implementation of OMG's UML/MOF specifications (more about that coming soon). The normative XMI files provide the full UML meta-model, featuring a number of multiple inheritances and dreaded diamonds. So, you can expect some hard times because of QObject
disabilities for handling virtual inheritance and templates (no mix-ins ? no traits ?). Furthermore, a design and analysis tool for handling MOF-based models is also been developed and, as such, should be highly reflective in order to not be tied to any specific meta-model. Qt's property system could easily do the trick, but keeping reading :).
Since a number of classes in UML meta-model are abstract, a possible solution to overcome QObject's multiple inheritance issues would be to postpone QObject
inheritance to lowest possible class in hierarchy. Also cross you fingers for not having a concrete class inheriting from two (or more) other concrete classes (actually, in UML this only occurs in 'AssociationClass'). Current implementation is somehow akin to:
QElement
is an "abstract" (protected constructor) class:
class Q_UML_EXPORT QElement { public: virtual ~QElement(); const QSet<QElement *> *ownedElements() const; ... protected: explicit QElement(); };
QNamedElement
is also an "abstract" (protected constructor) class:
class Q_UML_EXPORT QNamedElement : public virtual QElement { public: virtual ~QNamedElement(); QString name() const; void setName(QString name); QtUml::VisibilityKind visibility() const; void setVisibility(QtUml::VisibilityKind visibility); QString qualifiedName() const;[sourcecode] // read-only QNamespace *namespace_() const; ... protected: explicit QNamedElement(); };
QPackage
is a concrete class:
class Q_UML_EXPORT QPackage : public QObject, public QNamespace, public QPackageableElement, public QTemplateableElement // dreaded diamonds here { Q_OBJECT // From QElement Q_PROPERTY(const QSet<QElement *> * ownedElements READ ownedElements) // From QNamedElement Q_PROPERTY(QString name READ name WRITE setName) Q_PROPERTY(QString qualifiedName READ qualifiedName) Q_PROPERTY(QElement * namespace_ READ namespace_) public: explicit QPackage(QObject *parent = 0); virtual ~QPackage(); };
That's fine, even though generated documentation becomes somehow weird because of property accessors being implemented in base classes.
Issues arise, however, when implementing the analysis tool. It would be great if each property could be dynamically verified if it is a QNamedElement
and its name would be exhibited in a property editor. Something like:
... int propertyCount = currentElement->metaObject()->propertyCount(); for (int i = 0; i < propertyCount; ++i) { QMetaProperty property = element->metaObject()->property(i); QString typeName = property.typeName(); if (typeName.endsWith('*') and !typeName.contains(QRegularExpression ("QSet|QList"))) { if (QtUml::QNamedElement *namedElement = property.read(element).value<QtUml::QElement *>()) { tableItem->setText(namedElement->name()); } } ...
The sad thing is: in Qt4, values can be extracted from QVariant's only by using the same type used when constructing the QVariant
. Qt5 introduces a nifty feature where you can safely extract a QObject
* from any QVariant built from a QObjectDerived *. That would easily solve the issue above provided that QNamedElement
would inherit from QObject
:).
So, it seems the solution requires inheriting from QObject
early in hierarchy (QElement
) and get rid of multiple QObject
inheritance by using delegation. In addition, accessing 'sub-objects due to inheritance' and 'sub-objects due to delegation' by using a common API would be a plus. That's what I came up with:
class MyQObject : public QObject { Q_OBJECT public: explicit MyQObject(QObject *parent = 0); virtual ~MyQObject(); template <class T> friend inline T myqobject_cast(MyQObject *object); protected: QSet<MyQObject *> _subObjects; }; template <class T> inline T myqobject_cast(MyQObject *base) { if (dynamic_cast<T>(base)) return dynamic_cast<T>(base); foreach(MyQObject *myqobject, base->_subObjects) { T returnValue = myqobject_cast<T>(myqobject); if (returnValue != T()) return returnValue; } return dynamic_cast<T>(base); // not found }
A first MyQObject-derived class:
class Derived1 : public MyQObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName) public: explicit Derived1(QObject *parent = 0); QString name() const; void setName(QString name); private: QString _name; };
A second MyQObject-derived class (Derived2) would be defined similarly.
A class "multiple inheriting" indirectly from MyQObject
would be:
class Join : public MyQObject // no inheritance, no dreaded diamonds, 'smart delegation' instead { Q_OBJECT public: explicit Join(QObject *parent = 0); ~Join(); private: Derived2 *_derived2; Derived1 *_derived1; };
Join::Join(QObject *parent) : MyQObject(parent), _derived2(new Derived2), _derived1(new Derived1) { _subObjects.insert(_derived11); _subObjects.insert(_derived2); } Join::~Join() { delete _derived2; delete _derived1; }
That done, how a client would look like ?
Join *join = new Join; if (myqobject_cast<MyQObject *>(join)) qDebug() << "myqobject_cast<MyQObject *>(join) ok"; if (myqobject_cast<Derived1 *>(join)) qDebug() << "myqobject_cast<Derived1 *>(join) ok"; if (myqobject_cast<Derived2 *>(join)) qDebug() << "myqobject_cast<Derived2 *>(join) ok"; if (!myqobject_cast<Unrelated *>(join)) qDebug() << "myqobject_cast<Unrelated *>(join) fail";
That would remove the burden of creating tons of stub methods (if aggregated object is deeply lower in hierarchy) and one more level of indirection (how to simulate virtual methods ?). Yes, sub-objects representing the tip of dreaded diamonds would be duplicated but myqobject_cast
would return always the same sub-object.
I'm wondering how much of this could be automated by moc so that we could have something like:
class Join : public MyQObject, wraps Derived1, wraps Derived2 { Q_OBJECT public: explicit Join(QObject *parent = 0); ~Join(); };
And that's all, all hard work behind the scenes would be generated by moc :) Only a little change in perspective when using such object would be needed (think always in terms of myqobject_cast's).
So, too much engineering ? :) Any alternative solution ? Comments ?
See you !