我有一个抽象类,其中包含纯虚拟信号和从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)
将在任何接口的方法中工作。但在构造函数中,似乎是不可能的。
有没有办法做到这一点?
答案 0 :(得分:1)
一般来说,接口应该是抽象类,它们的构造函数根本不应该做任何事情。所以这是糟糕的设计。
如果你坚持这样做,那么C ++的语义就会阻止我们完全按你所说的那样去做。当您进行乘法继承时,dynamic_cast
到C
将失败,直到输入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}}部分是假的。
Interface
是一个具有空扩展的预处理器宏:对于编译器,它什么都不做。
signals
不是Interface
,因此被moc忽略。
QObject
仅对moc有意义,如果它位于两者的类中:
signals:
和QObject
宏。一般来说,虚拟信号没什么意义。它们都是“虚拟的”,因为你可以在不给编译器提供虚拟方法的情况下连接东西。
最后,它们也不能使用Qt 5经过编译时验证的语法,因为Q_OBJECT
不是具体的Interface
- 派生类具有正确的信号