1

使用 Qt 5.0.0

以下是大致的一种Observer模式(代码被剥离到最低限度以仅解释问题):

class A : public QObject
{
    Q_OBJECT
    public:
        void registerListner(Observer *pObs);
        static A* getInstance();
    signals:
        void sig();
};

void A::registerListner(Observer *pObs)
{
    connect(this, SIGNAL(sig()), pObs, SLOT(slo));
}

////////////////////////////////////////////////////////////////

class Observer : public QObject
{
    Q_OBJECT
    public slots:
        virtual void slo() = 0;
};

class ConcreteObserver : public Observer , public QWidget
{
    Q_OBJECT
    public: //re-mentioning "slots" is not necessary
        virtual void slo();
};

ConcreteObserver *pCObs = new ConcreteObserver;
A::getInstance()->registerListner(pCObs);

/////////////////////////////////////////////////////////////

问题(除了可怕的钻石):不能从 QObject 继承多次 - moc 不允许它。一种可能的解决方案是从 QWidget 派生 Observer,然后仅从 Observer 派生 ConcreteObserver。然而,这对 ConcreteObserver 施加了限制。也许 ConcreteObserver_2 需要从 QDialog 派生等等。

我该如何解决这个设计问题?Qt 5.0.0 Signal-Slot(除了早期版本)有什么特定的东西可以解决这个问题,或者你有什么建议?

4

1 回答 1

1

如果运行时警告对您来说还不够,您可以通过制作函数模板来添加一些编译时类型检查,并通过不定义类本身来registerListener避免多重继承。QObjectObserver

这可能看起来像这样:(注意:我的 SFINAE 技能不存在,这可能会变得更好。)

#include <QObject>
#include <QDebug>

#include <type_traits>

class A : public QObject
{
    Q_OBJECT
    public:
        template <typename T>
        void registerListener(T *pObs)
        {
            static_assert(std::is_base_of<QObject, T>::value,
                          "Listener must be a QObject");
            static_assert(std::is_same<void, 
                                       decltype(std::declval<T>().slo())
                                      >::value,
                          "Slot slo must have signature void slo();");
            connect(this, SIGNAL(sig()), pObs, SLOT(slo()));
        }
        static A* getInstance() { return instance; }
        static void init() { instance = new A; }
        void doStuff() { emit sig(); }
    signals:
        void sig();
    private:
        static A *instance;
};

几个测试用例:

class BadObject1 : public QObject
{
    Q_OBJECT
    public:
        BadObject1() {}
    public slots:
        void slo(int){}
};
class BadObject2 : public QObject
{
    Q_OBJECT
    public:
        BadObject2() {}
    public slots:
        int slo(){return 0;}
};
struct BadObject3 {
    void slo();
};

class ObservedObject : public QObject
{
    Q_OBJECT
    public:
        ObservedObject(QString const& name): QObject() {
            setObjectName(name);
        }
    public slots:
        virtual void slo(){
            qDebug() << objectName();
        }
};
class ObservedObject2 : public ObservedObject
{
    Q_OBJECT
    public:
        ObservedObject2(QString const& name)
          : ObservedObject(name + " (derived)") {}
};

还有一个主文件:

#include "A.h"

A* A::instance = 0;

int main(int , char **)
{
    A::init();
    A::getInstance()->registerListener(new BadObject1);
    A::getInstance()->registerListener(new BadObject2);
    A::getInstance()->registerListener(new BadObject3);
    A::getInstance()->registerListener(new ObservedObject("foo"));
    A::getInstance()->registerListener(new ObservedObject2("bar"));
    A::getInstance()->doStuff();
}

在所有情况下,您都会遇到编译器错误BadObjectN。如果您将它们注释掉,输出将如下所示:

"foo" 
"bar (derived)" 

但是有一个警告:这不会检查该void slo();成员是否确实是一个插槽。您可以在运行时通过以下方式检查:

if (pObs->metaObject()->indexOfSlot("slo()") == -1) {
    qDebug() << "Class" << pObs->metaObject()->className() 
                        << "doesn't have a slo slot.";
   ::exit(1);
}

这将起作用并完成预期的工作(除非您有一个未将插槽声明为虚拟的类层次结构 - 然后在省略slots“说明符”的派生类中会发生奇怪的事情。所以我主张您的文档没有您在上面对该说明符的评论:在重载插槽时使用它总是一个好主意)。

我不相信最后的检查在编译时是可以实现的,“槽解析”是通过 QObject 元数据的运行时遍历完成的,并且涉及解析 moc 生成的字符串。即使它使用了一些递归模板魔法,我也不认为这是工作的努力。您将在注册类型处收到运行时错误消息,您可以在其中包含错误对象的实际类名。这是一个非常准确的错误消息,应该被最简单的测试用例捕获。

于 2013-02-03T09:54:44.650 回答