【问题标题】:How to connect an abstract signal to a slot within the interface's constructor?如何将抽象信号连接到接口构造函数中的插槽?
【发布时间】:2015-06-26 17:18:37
【问题描述】:

我有一个包含纯虚拟信号的抽象类和一个派生自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) 将在任何接口的方法中工作。但是在构造函数内部,这似乎是不可能的。

有什么办法吗?

【问题讨论】:

  • 你遇到了什么错误?
  • @MatthewRead 感谢您的提问。 QObject::connect 中的 this 不是 QObject。
  • 哦,好吧,AbsView 需要扩展 QObject
  • 好吧,否决票合乎逻辑吗??!!在这个答案中,我只是以抽象类为例。如果是接口呢??
  • 对不起,是的,你是对的。

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


【解决方案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,并且接口将在运行时检查实现的信号实际上是一个信号,而不是其他方法。

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

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

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

  3. signals: 仅对 moc 有意义,前提是它位于 both 的类中:

    • 派生自QObject,并且
    • 包含Q_OBJECT 宏。

一般来说,虚拟信号意义不大。它们都是“虚拟的”,因为您可以在不给编译器提供虚拟方法的情况下连接事物。

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

【讨论】:

  • 其实“延迟连接”的部分已经在我的脑海里了。您的第一个解决方案是我所做的,但正在考虑更好的方法。这就是我在这里问这个问题的原因。无论如何,由于答案是正确的,我将其标记为正确答案。 +1 详细撰写。非常感谢..
猜你喜欢
  • 1970-01-01
  • 2012-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-03
  • 1970-01-01
相关资源
最近更新 更多