如何将抽象信号连接到接口的构造函数中的插槽?

时间:2015-06-26 17:18:38

标签: c++ qt inheritance multiple-inheritance signals-slots

我有一个抽象类,其中包含纯虚拟信号和从QObject派生的类。我想将该信号连接到派生类的插槽。

class MSys : public QObject
{
    Q_OBJECT
public:
    explicit MSys(QObject *parent = 0) : QObject(parent) {}
    virtual ~MSys() {}

public slots:
    void onRequset();
};

class AbsView
{
protected:
    AbsView() : m_sys(new MSys)
    {
    // QObject::connect(this, SIGNAL(request()), m_sys, SLOT(onRequset()));
    /* What can I do here !? */
    }

public:
    virtual ~AbsView() {}

signals:
    virtual void request() = 0;

private:
    MSys *m_sys;
};

Q_DECLARE_INTERFACE(AbsView, "AbsView")

一旦构造函数完成,这不是问题:那么dynamic_cast<QObject*>(this)将在任何接口的方法中工作。但在构造函数中,似乎是不可能的。

有没有办法做到这一点?

1 个答案:

答案 0 :(得分:1)

一般来说,接口应该是抽象类,它们的构造函数根本不应该做任何事情。所以这是糟糕的设计。

如果你坚持这样做,那么C ++的语义就会阻止我们完全按你所说的那样去做。当您进行乘法继承时,dynamic_castC将失败,直到输入C的构造函数为止:

struct Interface {
  Interface() { assert(dynamic_cast<QObject*>(this) == 0); }
};

struct C : public QObject, public Interface {
  C() { assert(dynamic_cast<QObject*>(this)); }
};

因此,我们需要一些延迟连接的方法,直到构造完整的对象,或者至少直到它的QObject部分可用为止。

一种简单的方法是接口明确要求构造基数:

struct Interface {
  Interface(QObject * base) {
    connect(base, ...);
  }
};

struct C : public QObject, public Interface {
  C() : Interface(this) {}
};

构造函数签名很好地表达了意图:Interface旨在用于派生自QObject的类。它不适用于那些没有的人。

另一种方法是延迟连接,直到事件循环有机会运行。如果不需要更快的连接,这是可以接受的。这不需要将Interface构造函数传递给基类的显式指针。

计时器由当前线程的事件调度程序拥有,这样即使事件循环没有启动它也不会泄漏。

class Interface {
public:
  virtual void request() = 0; // entirely optional
  Interface() {
    auto timer = new QTimer(QAbstractEventDispatcher::instance());
    timer.start(0);
    QObject::connect(timer, &QTimer::timeout, [this, timer]{
      timer.deleteLater();
      connect(dynamic_cast<QObject*>(this), SIGNAL(request()), ...);
    });    
  }
};

请注意,在所有情况下,将信号在界面中声明为虚拟是完全多余的。 Interface的构造函数可以检查信号是否存在,并断言它,甚至始终abort()

仅仅通过声明虚拟信号并不能保证信号实际上是一个信号。即使编译器没有提供相反的诊断,以下内容也不起作用:

struct Interface {
  signals:
    virtual void aSignal() = 0;
};

struct Implementation : public QObject, public Interface {
  void aSignal() {} // not really a signal!
};

这将在运行时检测到丢失的信号:

struct Interface {
  // No need for virtual signal!
  Interface(QObject * base) {
    Q_ASSERT(base->metaObject()->indexOfSignal("request()") != -1);
  }
};

struct Implementation : public QObject, public Interface {
  Q_OBJECT
  Q_SIGNAL void reuest(); // a typo
  Implementation() : Interface(this) {} // will assert!
};

确保遵循虚拟信号的最佳方法可能是两者兼而有之,并将信号实现声明为覆盖:

struct Interface {
  virtual void aSignal() = 0;
  Interface(QObject * base) {
    Q_ASSERT(base->metaObject()->indexOfSignal("request()") != -1);
  }
};

struct Implementation : public QObject, public Interface {
  Q_OBJECT
  Q_SIGNAL void request() Q_DECL_OVERRIDE;
  Implementation() : Interface(this) {}
};

即使你从未实例化Implementation,编译器也会捕获拼写错误,并且接口将在运行时检查实现的信号实际上是一个信号,而不是其他方法。

还必须注意signals:的{​​{1}}部分是假的。

  1. Interface是一个具有空扩展的预处理器宏:对于编译器,它什么都不做。

  2. signals不是Interface,因此被moc忽略。

  3. QObject仅对moc有意义,如果它位于两者的类中:

    • 派生自signals:
    • 包含QObject宏。
  4. 一般来说,虚拟信号没什么意义。它们都是“虚拟的”,因为你可以在不给编译器提供虚拟方法的情况下连接东西。

    最后,它们也不能使用Qt 5经过编译时验证的语法,因为Q_OBJECT不是具体的Interface - 派生类具有正确的信号