可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to connect some object's signals derived from an interface class. The connection is done in QWidget::listenToAnimal(AnimalInterface*)
. This does not work because qt_metacall is not a member of 'AnimalInterface'
and static assertion failed: No Q_OBJECT in the class with the signal
.
Of course AnimalInterface
does not have the Q_OBJECT macro and does not inherit QObject because it is an interface...
I want to connect through the interface class because I do not want to manually retype the same code for Cat
and for Dog
.
Is it possible to connect the signal the way I want to? Perhaps with templates? Is this perhaps a lambda
-specific problem?
header:
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> class AnimalInterface{ public: virtual ~AnimalInterface(); virtual void makeSound() = 0; /*signals*/ virtual void madeSound() = 0; }; Q_DECLARE_INTERFACE(AnimalInterface,"interface") class Dog : public QObject, public AnimalInterface { Q_OBJECT Q_INTERFACES(AnimalInterface) public: void makeSound(); signals: void madeSound(); }; class Cat : public QObject, public AnimalInterface { Q_OBJECT Q_INTERFACES(AnimalInterface) public: void makeSound(); signals: void madeSound(); }; class Widget : public QWidget { Q_OBJECT Cat *cat_; Dog *dog_; public: Widget(QWidget *parent = 0); ~Widget(); void listenToAnimal(AnimalInterface *animal); }; #endif // WIDGET_H
cpp:
#include "widget.h" #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent) { dog_ = new Dog; cat_ = new Cat; listenToAnimal(dog_); listenToAnimal(cat_); dog_->makeSound(); cat_->makeSound(); } void Widget::listenToAnimal(AnimalInterface *animal) { connect(animal, &AnimalInterface::madeSound, this, [](){ qDebug()<<"animal made sound"; }); } Widget::~Widget() { } void Cat::makeSound() { qDebug()<<"Cat says miaow"; emit madeSound(); } void Dog::makeSound() { qDebug()<<"Dog says wuff"; emit madeSound(); }
main.cpp
#include "widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }
回答1:
Since you know the derived type at compile type, you can connect to the proper, statically-known QObject
-derived type. No need for dynamic casting or anything of the sort. You just don't want the listenToAnimal
method to be available for non-AnimalInterface
-inheriting types, though, even if it they have a compatible madeSound
method:
C++11
#include <type_traits> template< class T, typename = typename std::enable_if<std::is_base_of<AnimalInterface, T>::value>::type > void listenToAnimal(T * animal) { connect(animal, &T::madeSound, this, []{ qDebug() << "animal made sound"; }); }
C++03
template <class T> void listenToAnimal(T * animal) { Q_UNUSED(static_cast<AnimalInterface*>(animal)); connect(animal, &T::madeSound, this, &Widget::onAnimalMadeSound); }
You can then use it without having to spell out the type - it's already known to the compiler:
listenToAnimal(dog_); listenToAnimal(cat_);
If the derived type is not known at compile time, you have to dynamically cast to QObject
and connect by name, not by method pointer. It will assert at runtime if you've passed in a wrong type - after all, it's not enough for it to be an instance of AnimalInterface
, it also needs to be a QObject
instance.
void listenToAnimal(AnimalInterface * animal) { auto object = dynamic_cast<QObject*>(animal); Q_ASSERT(object); connect(object, SIGNAL(madeSound()), this, SLOT(onAnimalMadeSound())); }
The fact that the type AnimalInterface
has a virtual madeSound
method is somewhat relevant - it guarantees that the derived class implements the method with such a signature. It doesn't guarantee that the method is a signal, though. So you should probably rethink your design and ask yourself: "What do I gain by using a static type system when I can't really use it for static type checking"?
Most likely you should make any methods that would nominally accept the AnimalInterface*
, be parametrized and take a pointer to the concrete class. Modern code generators and linkers will deduplicate such code if type erasure leads to identical machine code.
回答2:
Found a solution with templates. Did not work the first time I tried, obviously did something wrong first. Here it goes...
Just replace the corresponding parts from the example in the question (and remove definition of listenToAnimal
from the source file):
header:
template<class T> void listenToAnimal(AnimalInterface *animal) { T *animal_derivate = dynamic_cast<T*>(animal); if (animal_derivate){ connect(animal_derivate, &T::madeSound, this, [](){ qDebug()<<"animal made sound"; }); } }
cpp:
listenToAnimal<Dog>(dog_); listenToAnimal<Cat>(cat_);
Update:
After trying Kuba Ober's answer, it seems like this is working best now:
template<typename T> typename std::enable_if<std::is_base_of<AnimalInterface, T>::value,void>::type listenToAnimal(T *animal) { connect(animal, &T::madeSound, this, [](){ qDebug()<<"animal made sound"; }); }
However, the one point still not working is how to connect if I create an animal like AnimalInterface *bird = new Bird
, because it throws the same error that the base class does not have the signal.