Connection of pure virtual signal of interface class

匿名 (未验证) 提交于 2019-12-03 08:57:35

问题:

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.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!