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

How to connect an abstract signal to a slot within the interface's constructor?

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

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) 将在任何接口的方法中工作。但是在构造函数中,这似乎是不可能的。

有什么办法吗?

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

如果您坚持这样做,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 派生的 classes。它不适用于那些不适用的人。

另一种方法是延迟连接,直到事件循环有机会 运行。如果在此之前不需要连接,这是可以接受的。这不需要向 Interface 构造函数传递一个指向基 class.

的显式指针

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

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,编译器也会捕获拼写错误,并且接口会在运行时检查实现的信号实际上是信号,而不是其他方法。

还必须注意 Interfacesignals: 部分是伪造的。

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

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

  3. signals: 仅对 moc 有意义,前提是它在 class 中 both:

    • 源自 QObject,并且
    • 包含 Q_OBJECT 宏。

一般来说,虚拟信号没有什么意义。它们都是 "virtual",因为你可以在不给编译器一个虚拟方法的情况下连接事物。

最后,它们也不适用 Qt 5 编译时验证的语法,因为 Interface 不是具体的 QObject-派生 class 并带有适当的信号