jump to navigation

QObject, multiple inheritance, and smart delegators October 29, 2012

Posted by Sandro Andrade in planetqt-sandroandrade.
trackback

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; // 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 !

Comments»

1. Anon - October 31, 2012

Why can’t QObject be used with multiple inheritance? Why can’t this be fixed instead?

Sandro Andrade - October 31, 2012

Because that would incur in too much (complex, painful) work to be done by moc. Furthermore,
when multiple inheriting from a (only one) QObject-derived and other non-QObject-derived classes,
the QObject-derived class should be the first inherited class. That’s because of memory layout
issues for inheritance and would probably make qobject_cast implementation quite difficult if
not required.

See: http://qt-project.org/doc/qt-5.0/moc.html#multiple-inheritance-requires-qobject-to-be-first

2. Benoit - November 20, 2012

That’s the kind of fun you eventually meet when extensively using QObject for complex structures (which we all want to :)). I find your solution interesting, although I am tempted to thing that there is something wrong when you need such constructions…

One of the drawbacks here is that it requires to explicitly cast to a base class in order to access its properties and methods (if I understand it correctly). The main advantage is that it can properly handle multiple inheritance, even for concrete objects, while keeping the derived class free of stub-methods…

I guess that moc support would be very tricky and maybe even involves extending QObject, too.

Another idea would be to have some kinds of abstract properties, being also supported by non-QObject types (with additional moc support using for example Q_GADGET).

That could be something like:

class QElement // not a QObject!
{
Q_GADGET
Q_ABSTRACT_PROPERTY( QString name READ WRITE )

protected:
QElement();
};

The QMetaProperty objects still belong to the concrete class (“QPackage”) but the meta object could provide information about the base class the property belongs to, while moc could generate an error when an abstract property is not implemented.

Regarding the issue with QVariant (which is the reason why you are “forced” to use QObject), I did not really get it. Wouldn’t it be working if the namespace_() method returned a QElement* (instead of QNamespace*) and the type had been checked with a dynamic_cast?

Anyway, if there is absolutly no way to avoid using QObject for the base classes, your solution is smart and seems to be the best, far better than using stub-methods… There might be some alternatives using templates, but it would make many developers (like me) run away🙂

Sandro Andrade - November 20, 2012

Hi Benoit,

Yes, Q_GADGET would be an option, but I couldn’t find any use of Q_ABSTRACT_PROPERTY in qt5 codebase. Does it already exist or are you wondering some moc extension ?🙂

Regarding QVariant issue, you can’t write a

v.value<QElement *>()

if you have created the QVariant by writing, for example:

QVariant::fromValue<QNamedElement *>(...),

provided that QNamedElement inherits QElement. That only works in Qt5 AND if all involved objects inherit from QObject. Of course, I’m also assuming you aren’t willing to write some weird code like:

QNamedElement *element = dynamic_cast<QNamedElement *>((QElement *) v.data())

🙂

I’ve made some improvements on client-side, by defining a QtUml-specific smart pointer. I’ll blog about that soon🙂

Thanks for reply

3. Benoit - November 20, 2012

Hi Sandro,

thanks for the explanations! Yes, the “abstract property” was just an idea of how moc could be extended.

Regarding the QVariant topic, what I mean is that you only need QVariant to access the property value via QMetaProperty. In that case, I guess that the QVariant is initialized using the type given when declaring Q_PROPERTY and it would be sufficient to always use the base type (QElement). You only need then a cast to get a pointer to a derived class (which is anyway needed).

QtUml::QElement * element = property.read(element).value();

QtUml::QNamedElement * namedElement = dynamic_cast(element);

But I probably missed something🙂

Benoit.

Benoit - November 20, 2012

Seems that some parts of the code was interpretated as HTML formatting, let me write the examples again:


QtUml::QElement * element = property.read(element).value();

and

QtUml::QNamedElement * namedElement = dynamic_cast(element);

Benoit - November 20, 2012

After reading WordPress support page, I can now finally embed source code, last try for today🙂

QtUml::QElement * element = property.read(element).value<QtUml::QElement *>();

and

QtUml::QNamedElement * namedElement = dynamic_cast<QtUml::QNamedElement *>(element);
4. Call for arms: QtMof/QtUml « Live Blue - December 11, 2012

[…] roughly mentioned in a previous post, in last two months I’ve been working on a Qt5-based implementation of OMG’s MOF (Meta […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: