如果运行时警告对您来说还不够,您可以通过制作函数模板来添加一些编译时类型检查,并通过不定义类本身来registerListener
避免多重继承。QObject
Observer
这可能看起来像这样:(注意:我的 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 生成的字符串。即使它使用了一些递归模板魔法,我也不认为这是工作的努力。您将在注册类型处收到运行时错误消息,您可以在其中包含错误对象的实际类名。这是一个非常准确的错误消息,应该被最简单的测试用例捕获。