QObject, multiple inheritance, and smart delegators

Planet Qt Oct 29, 2012

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 !

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.