写在前面:本博客为本人原创,严禁任何形式的转载!本博客只允许放在博客园(.cnblogs.com),如果您在其他网站看到这篇博文,请通过下面这个唯一的合法链接转到原文!
本博客全网唯一合法URL:http://www.cnblogs.com/acm-icpcer/p/6821921.html
Windows编程之connect函数研究
#include <iostream> using namespace std; //第四步才看 class A; class B; typedef void (A::*Apointer)(); typedef void (B::*Bpointer)(); //第一步开始看 class A { public: void (A::*click)();//function pointer for class void onClicked(){ cout<<"A button,A onClick function"<<endl; } //第四步才看 B* slotObj; void TreatClickEvent(){ (slotObj->*(Bpointer)click)(); } }; //第三步才看 class B { private: int x=5; public: //int x=5; void onClicked(){ cout<<"A button,B onClick function,x is:"<<x<<endl; } }; //第一步开始看:复习成员变量指针 void runMemberVariblePointer(A * obj, int A::* pMember) { cout<<obj->*pMember<<endl; } //第一步开始看:复习成员函数指针 void runfuncName(A * obj, void (A::*func)()){ (obj->*func)(); } //第一步看:组合成员变量指针和成员函数指针 void runfuncPointer(A * obj, void (A::*( A::*pfunc ))()){ //Apointer A::* pfunc (obj->*(obj->*pfunc))(); } //typedef void (A::*Apointer)(); //第二步才看 //typedef void (A::*(A::*Signal))(); typedef Apointer A::* Signal; void connect(A* a, Signal signal, Apointer slot){ //void (A::*slot)() a->*signal = slot; } //第三步才看 void connect(A* a, Signal signal, Bpointer slot){ a->*signal = (Apointer) slot; } //第四步才看 void connect(A* a, Signal signal, B* b, Bpointer slot){ a->*signal = (Apointer) slot; a->slotObj = b; } int main(int argc, char *argv[]) { //第一步、理解信号的本质:成员函数指针类型的特殊成员变量 //第二步、连接A本身的信号与槽 A a; runfuncName(&a,&A::onClicked); connect(&a,&A::click,&A::onClicked);//a.click = &A::onClicked; //refers to : connect(A* a, Signal signal, Apointer slot) (a.*a.click)(); runfuncPointer(&a,&A::click); //第三步:连接A的信号到B的槽 B b; B * fb = &b; connect(&a, &A::click, &B::onClicked);//a.click = (void (A::*)())&B::onClicked; (a.*a.click)(); (b.*(Bpointer)a.click)();//(fb->*(Bpointer)a.click)(); //第四步:完善连接A的信号到B的槽 connect(&a, &A::click, fb, &B::onClicked); a.TreatClickEvent(); return 0; }
直接看主函数。
主函数的代码简要看分成三个部分:
(1)
第一个部分最开始就创建了A类对象a。
首先做好必要的准备工作,即调用runfuncName函数,通过函数形参我们可以看出这个函数主要是通过传入对象指针及函数指针来调用对象的成员函数(在此处实现了调用a的onClicked函数打印提示信息)。
然后通过connect函数连接a对象中的函数指针click和函数onClicked,那么通过看函数形参我们可以知道这个connect函数的功能就是就是将信号(即函数指针click)和槽(即a对象中的函数onClicked)连接在一起,这样之后,调用click指针寻址后就可以调用a对象中的函数onClicked;效果就像是通过click这个信号触发了onClicked函数。而紧随其后的代码(a.*a.click)();就是起到了检查信号和槽是否连接成功的作用。
那么后面的函数runfuncPointer(&a,&A::click);又有什么作用呢?
把目光转向函数形参:void runfuncPointer(A * obj, void (A::*( A::*pfunc ))())
形参中的A * obj自然指要传一个A类对象的指针进来;而void (A::*( A::*pfunc ))()则表示一个指向函数指针的指针。
而函数体内的语句(obj->*(obj->*pfunc))();则是通过通过这个指向函数指针的指针来找到这个函数指针(即对象a中的信号click),从而通过这个信号触发其对应的槽(即a对象中的函数onClicked)。而为了成功执行这条语句,之前就必须通过函数connect将信号与槽连接起来,否则编译器执行到此处时报错。
那么讲到这里我们就一目了然了:函数runfuncPointer(&a,&A::click);本质上也是用来检查信号和槽是否连接成功的。实际上,这条语句被执行后和代码(a.*a.click)();一样都调用了a对象的函数onClicked,打印出提示信息A button,A onClick function。
(2)
第二个部分与第一个部分的实现目的基本一致,区别在于第二个部分希望通过A类对象的信号去驱动B类信号的成员函数。
故而主要看代码connect(&a, &A::click, &B::onClicked);
这个connect函数的形参为connect(A* a, Signal signal, Bpointer slot),它与第一部分的connect函数的不同之处在于函数体内将A类对象的信号与B类信号的槽连接:a->*signal = (Apointer) slot;,但是明显,A类信号不能直接与B类槽连接,所以在B类槽前面需要写明A类的槽的类型,以此进行强制类型转换。后面的触发信号语句也说明了这一点:
(b.*(Bpointer)a.click)();
其中,加粗语句将A类信号转换为B类信号。这条语句等价于:(fb->*(Bpointer)a.click)();(fb是B类对象的指针)。
(3)
第二部分没能实现访问B类的数据成员,比如语句(a.*a.click)();输出的x的值为随机值。
那么,为了真正的将不同类之间的信号和槽连接在一起,需要重写connect函数:
void connect(A* a, Signal signal, B* b, Bpointer slot){
a->*signal = (Apointer) slot;
a->slotObj = b;
}
connect(&a, &A::click, fb, &B::onClicked);
可以看出,这个connect函数与第二部分的connect函数的最大不同只在于多写了一句a->slotObj = b;,那么这句话将A类对象内部的B类对象指针指向指定的B类对象,以便在A类对象内部通过函数TreatClickEvent触发信号,以真正的连接不同类之间的信号与槽。
这之后,用语句a.TreatClickEvent();在对象中触发信号,便可以真正的访问B类对象的数据成员了。
by TZ@华中农业大学
2017/5/7