実行時の警告では不十分な場合は、関数テンプレートを作成してコンパイル時の型チェックを少し追加し、クラス自体を定義しないこと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で生成された文字列の解析が含まれます。再帰的なテンプレートマジックを使用したとしても、それがうまくいくとは思いません。登録タイプで実行時エラーメッセージが表示され、障害のあるオブジェクトの実際のクラス名を含めることができます。これは非常に正確なエラーメッセージであり、最も単純なテストケースでキャッチする必要があります。