Academic Solutions to Academic Problems

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 15 | Документация


by Volker Hilsheimer

The moc tool and QObject add flexibility and power to the C++language. They add, among other things, the signals and slots paradigmand QObject's memory management mechanism. Both are very powerful andsometimes we wish we could use them in template classes and inconjunction with multiple inheritance.

Содержание

In this article, we will look closer at why moc does not supportthese use cases and how to work around these restrictions inpractice.

[править] Connecting to Templates

The meta-object compiler, moc, requires a class's API to becompletely defined by its definition; moc can then parse thedefinition and create a "meta-object" that stores informationabout all the properties, signals, and slots of that class.

A template class is not defined only by definition but also byinstantiation. The template class itself is just a blueprint that theC++ compiler uses to generate the actual C++ classes later on.There can be an arbitrary number of instantiations with differenttemplate parameters, and each of these instantiations would require adifferent meta-object.

While it is theoretically possible for moc to handle templates, itwould be extremely complex to implement, and would be highly impracticalto use: For each template instantiation, moc would have to generatethe appropriate meta-object code, and the generated code would have tobe included once per link unit---which becomes a nightmare to maintainonce a template class is used with the same template parameter indifferent compilation units.

Therefore, running moc on a file declaring a template class likethe one below prints a friendly error message ("Sorry, Qt does notsupport templates that contain signals, slots or Q_OBJECT"): // WON'T WORKtemplate <typename T>class ATemplateClass : public QObject{ Q_OBJECTpublic: ATemplateClass(QObject *parent = 0);public slots: void setValue(const T &value);signals: void valueChanged(const T &value); ...private: T myValue;}; If the signals and slots don't require the template parameter to be part ofthe prototype, the workaround is to make a template class inherit a QObject subclass that provides the required signals and slots:

class AQObjectSubclass : public QObject
{
    Q_OBJECT
 
public:
    AQObjectSubclass(QObject *parent = 0);
 
public slots:
    void setValue(const QString &amp;value);
 
signals:
    void valueChanged(const QString &amp;value);
    ...
};
 
template <typename T>
class ATemplateClass : public AQObjectSubclass
{
public:
    ATemplateClass(QObject *parent = 0);
 
    void someOtherFunction(const T &amp;value);
    ...
};

If signals and slots need to use the template parameters, theObserver pattern is an alternative.

[править] The Observer Pattern

The Observer pattern is a mechanism for notifying changes of state inone object to one or more other objects. In Qt programming, it isseldom used, because signals and slots fulfill that role very well.

Instead of emitting signals, an observable class calls virtual functionson a list of "observers" (or "listeners"). The observers are objectsthat implement a particular interface and respond to it. For example:

template <typename T>
class AnObserver
{
public:
    void valueChanged(const T &amp;value) = 0;
};
 
template <typename T>
class ATemplateClass
{
public:
    void installObserver(AnObserver *observer)
        { myObservers.append(observer); }
    void removeObserver(AnObserver *observer)
        { myObservers.removeAll(observer); }
 
    void setValue(const T &amp;value) {
        if (myValue != value) {
            myValue = value;
            foreach (AnObserver *observer, myObservers)
                observer->valueChanged(myValue);
        }
    }
 
private:
    T myValue;
    QList<AnObserver *> myObservers;
};

Compared to signals and slots, the Observer pattern is quiteheavyweight: It requires us to manually keep a list of observers, toprovide functions for installing and removing observers, and to iteratethrough that list whenever we want to notify the observers (theequivalent of emitting a signal). It also requires us to subclass theobserver class whenever we want to be notified of a change in theobservable class (the "subject" class).

Although Qt is mostly signal--slot based, it uses the Observerpattern for event filters. Event filters are installed by calling QObject::installEventFilter(); afterwards, events are deliveredto the observers through the QObject::eventFilter() virtualfunction. In this case, the observer class and the subject class bothinherit the same class ( QObject).

[править] Multiple Inheritance

Inheriting from multiple QObject superclasses not only makes themeta-object difficult to generate, it also complicates the concept ofobject ownership. Indeed, inheriting multiple times from QObject will generate a compile error when using the Q_OBJECTmacro, or later on when compiling the meta-object code. All thoseerrors point at ambiguities in how to cast to QObject or how touse QObject's API.

The most common usage pattern for using multiple inheritance in C++is for implementing interfaces. Interfaces are abstract classes,where each function is declared as pure virtual. These functions arethen implementated in a separate class that is usually a subclass of oneor more interface classes. This pattern enforces a strict separation ofa well-defined public API from the internal implementation details. Qt4's plugin system is based on interfaces.

For example:

class ClipboardInterface
{
public:
    virtual void cut() = 0;
    virtual void copy() const = 0;
    virtual void paste() = 0;
};

The class implementing the above interface can be a QObjectsubclass, and even implement the pure virtual functions as slots:

class CustomWidget : public QWidget,
                     public ClipboardInterface
{
    Q_OBJECT
 
public:
    CustomWidget(QWidget *parent = 0);
 
public slots:
    void cut();
    void copy() const;
    void paste();
};

However, it would be nicer if the interface class itself were ableto declare the functions as slots---this would make implementing theinterface less error-prone and would also allow the interface tosupport introspection.

But making the interface class a QObject introduces the problemof multiple inheritance from QObject as soon as the interface isimplemented by a QObject, as would be the situation in theCustomWidget case.

To avoid the multiple inheritance, we can use a "has a" relationship:

class CustomWidget : public QWidget,
                     public ClipboardInterface
{
    Q_OBJECT
 
public:
    CustomWidget(QWidget *parent = 0);
 
    void cut();
    void copy() const;
    void paste();
 
private:
    ClipboardWrapper *wrapper;
};

The CustomWidget class "has a" ClipboardWrapper object.Here's the ClipboardWrapper class definition:

class ClipboardWrapper : public QObject,
                         public ClipboardInterface
{
    Q_OBJECT
 
public:
    ClipboardWrapper(QObject *parent)
        : QObject(parent)
    {
        Q_ASSERT(parent);
        wrappedObject =
            qobject_cast<ClipboardInterface *>(parent);
        Q_ASSERT(wrappedObject);
    }
 
public slots:
    void cut() { wrappedObject->cut(); }
    void copy() const { wrappedObject->copy(); }
    void paste() { wrappedObject->paste(); }
 
private:
    ClipboardInterface *wrappedObject;
};

Code that would usually connect to the CustomWidget object caninstead connect to the CustomWidget's wrapper object. The wrapperobject can communicate with the wrapped object through a well-definedinterface, and changes to the interfaces will trigger compile-timeerrors that help the software developer implement the interfacescorrectly in the different places it is used.

Adding signals follows a similar pattern:

class ClipboardEvents
{
public:
	virtual void copyAvailableChange(bool available) = 0;
	virtual void pasteAvailableChange(bool available) = 0;
};
 
class ClipboardWrapper : public QObject,
                         public ClipboardInterface§,
                         public ClipboardEvents
{
	Q_OBJECT
 
public:
	ClipboardWrapper( QObject *parent);
 
public slots:
	void cut() { wrappedObject->cut(); }
	void copy() const { wrappedObject->copy(); }
	void paste() { wrappedObject->paste(); }
 
signals:
	void copyAvailableChange(bool available);
	void pasteAvailableChange(bool available);
 
private:
	ClipboardInterface *wrappedObject;
};

Since the CustomWidget class must emit the signals from its owncode, and since signals are protected functions, we must use a castto call the signals through the interface declaration (which declaredthe functions as public):

    void CustomWidget::paste()
    {
        bool wasAvailable = isCopyAvailable();
        ...
        if (wasAvailable != isCopyAvailable())
            emit static_cast<ClipboardEvents *>(wrapper)->
                       copyAvailableChange(isCopyAvailable());
    }

At first sight, this pattern might appear to be over-engineered, but itscales very well as soon as many classes use the sameinterface/wrapper pair, and as soon as application code can useidentical APIs for many different objects.