当前位置: 首页 > 科技观察

军事物联网技术:C++仿真实现Qt的信号和槽机制

时间:2023-03-22 14:44:35 科技观察

对于大多数研究Qt的朋友来说,心中都有一种好奇——Qt的核心信号和槽是如何实现的,以及对小编自己来说也是一样的,当然大家肯定会去查阅相关资料,但是很多时候只是一知半解,想要自己去实现,就会一头雾水;所以小编决定用C++实现一个简单版的signalslot,以了解Qt的实现原理。于是小编就在翻看各个好朋友的博客,反复研究Qt源码重写,交流学习。下面简单梳理一下Qt信号和槽的实现机制:在Qt中实现信号和槽最重要的是使用元对象系统(MOS)的元对象编译器(MOC)来使用信号和槽我们定义。在槽类中定义信号以及信号调用槽函数的方法(这一步会生成源文件对应的moc_xx.cpp文件),然后建立信号与信号的一一对应关系槽通过系统关系提供的关联方法(connect),当一个信号发出时(实际上是调用一个信号函数),会通过信号与槽的对应关系找到并调用对应的槽函数。这样做的好处是用户不用关心初学者不容易搞清楚的函数指针回调函数,简化了用户的操作。当然,就像我们在享受幸福生活的时候,一定有人在背后默默耕耘,砥砺前行!这里也是一样,为了我们的用户简化操作,为了达到这个效果,我们需要在后台提供更多的支持。接下来我们通过代码再次梳理一下。首先,我们在使用信号槽的时候,肯定会有信号的发送者和接收者,所以我们先定义这两个类对象:sender.h#pragmaonce#include"object.h"classSender:publicObject{X_OBJECTpublic:Sender(intn=0):m_num(n){}voidsendSig();signals:voidholdClass(intn);intm_num;};sender.cpp#include"sender.h"voidSender::sendSig(){std::cout<<"发送信号:holdClass"<mp.equal_range(idx);for(autoit=ptr.first;it!=ptr.second;it++){MetaObject::Connectioncon=it->second;con.m_receiver->metaCall(con.method,argv);//调用receiver和sender信号关联的方法,并传递需要的参数}}voidObject::connect(Object*sender,constchar*s1,Object*receiver,constchar*s2){intsig_idx=-1,slt_idx=-1;MetaObject*senderMeta=sender->getMetaObject();//获取发送端保存的元对象MetaObject*receiverMeta=receiver->getMetaObject();//获取接收端保存的元对象//比较信号名称,找到对应的信号索引for(inti=0;isigs.size();i++){if(0==strcmp(s1+1,senderMeta->sigs[i].c_str())){签名_idx=i;}}//这里确认是槽函数,并找到对应的槽函数索引//如果有信号关联的信号,需要找到接收器对应的信号索引,这里省略if('1'==*s2){for(inti=0;islts.size();i++){if(0==strcmp(s2+1,receiverMeta->slts[i].c_str())){slt_idx=i;}}}if(-1==sig_idx||-1==slt_idx){std::cout<<"nomatchsigorslt"<mp.insert(std::make_pair(sig_idx,con));}//下面主要是预留方便的parentclasses调用子类重写方法的接口可以在这里简单定义。voidObject::metaCall(intidx,void**ag){}MetaObjectObject::meta;MetaObject*Object::getMetaObject(){return&meta;}接下来是MOC生成moc_xx.cpp,这些文件在Qt中自动生成,我们不需要实施它们。我只能手动模拟发送方的moc_sender.cpp和接收方的moc_receiver.cpp的简单实现。最后我们编译程序,需要将这两个编译在一起才能通过moc_sender.cpp#include"sender.h"//按照定义的信号槽顺序保存信号和槽函数名,Qt会单独保存函数名参数,这里简单模拟如下staticconstchar*sigs_name[]={"holdClass(int)"};staticconstchar*slts_name[]={nullptr};//为空表示当前没有定义对应的函数staticstd::vectorsigs(sigs_name,sigs_name+1);staticstd::vectorslts;MetaObjectSender::meta={sigs,slts};//Sender的信号定义voidSender::holdClass(intn){void*arg[]={(void*)&n};//调用MetaObject的静态方法activate传递当前信号sender对象,信号索引和参数MetaObject::activate(this,0,arg);//0表示当前信号函数在sigs_name[]中的索引}MetaObject*Sender::getMetaObject(){return&meta;//返回Sender的元对象}voidSender::metaCall(intidx,void**arg){//这里我们的Sender中没有槽函数,所以这里没有任何操作}moc_receiver.cpp#包括"receiver.h"staticconstchar*sigs_name[]={nullptr};staticconstchar*slts_name[]={"attendClass(int)"};staticstd::vectorsigs;staticstd::vectorslts(slts_name,slts_name+1);MetaObjectReceiver::meta={sigs,slts};MetaObject*Receiver::getMetaObject(){return&meta;//返回Receiver的元对象}voidReceiver::metaCall(intidx,void**arg){//根据slts_name[]中的索引值调用对应的slot函数if(0==idx){intn=*((int*)arg[0]);attendClass(n);}}有了以上文件,我们只需要将所有的.cpp文件一起编译运行,就可以实现Qt中信号槽的效果:g++object.cppsender.cppreceiver.cppmoc_sender.cppmoc_receiver.cppmain。cpp-oxuedao也可以用其他可用的编译器编译。这里直接使用g++。另外,如果一个对象修改或增删信号或槽,需要手动修改对应的moc_xx.cpp文件。考虑到Qt中实际问题会比较多。这里我们只是梳理一下整个信号槽关联和调用流程框架。具体可以参考Qt源码深入学习。