时隔将近半年的时间,想想应该说声抱歉,总感觉这抱歉有些无力,还是算了,大家都是搞技术的,所以还是直接来点实际点的吧。关于上一讲的内容已忘记得差不多,也懒得去纠结,不论是说过的亦或是没说过的我们就当重新开始,以便能够更好的连贯,一句话,我们的所说的东西都是这个Frame里的内容。
原本打算先和大家说怎么在C++里面操作的lua的,毕竟这个Frame的主要功能便是靠lua实现的,但是想想可能有些朋友对lua不甚了解所以在说怎么和lua通信之前那么大家可以先了解lua是个什么鬼,哦,对了,我用的是5.3版本的,虽然这个版本没什么第三方库,但是现在很多常用的东西我自己已经添加好,所以顺手了也就无所谓了。 那么今天我们从什么地方入手呢?嗯,好吧,就从“属性”这玩意而已开始吧,属性这个东西可能不是大家想想的属性,我取名字这事已经被同事吐槽了不下10次了,总觉得我名字莫名其妙,好吧,不说这些,先来认识下我所谓的属性到底是个什么鬼吧。 假设对象A表示一个矩形,那么他将有长和宽的概念,另一个对象B表示一个椭圆,当对象A的尺寸发生变化时而对象B能够知道A的尺寸发生变化而做出相应的改变,这就是我所谓的属性的概念,长和宽是矩形的属性,属性改变将会通知关心他属性的对象所作相应的动作,现在我用代码模拟这种情况出来: //=================================== class A{ public: void SetValue(const MString& value){ if(mValue == value) return; mValue = value; } private: MString mValue; }; class B{ public: void setValue(const MString& value){ if(mValue == value) return; mValue = value; } private: MString mValue; }; void testFun(const MString& value){ ErrorBox('testFun called') } int main(){ A objA; B objB; objA.SetValue(123); return 0; } //====================================== 现在我们要做的事就是当A类的SetValue被调用时B类的setValue也相应的被调用,也就是A的mValue的值被修改时那么B也应当修改自己的mValue值,现在我们要解决的就是该问题。 解决该问题的方法可能有很多,如果在Qt里面我们可以考虑使用信号槽来解决,在MFC中可以考虑使用事件通知来完成,在C#里面使用事件委托来完成,嗯不过好像不论是哪一种方式感觉都不是最理想的,因为如果使用信号槽那么如果我们需要观察多个属性的话可能会要定义好多个信号,同样如果使用事件的话那么我们同样需要定义很多事件类型,所以这些都不是一个一劳永逸的方法,这都是我所认为最理想的方法。 大家可能想起了前面我们所说的boost里面的信号槽,不错的主意,但是这个Qt的信号槽本身是差不多的东西,所以一样不满意,当然最重要的一点,如果我们通过objA调用SetValue的时候会触发objB的setValue,而objB在调用setValue的时候就会触发testFun,那么无论是Qt的信号槽还是boost的信号槽都没法做到,那么好吧,这样一来似乎没有什么选择的余地了,要么是函数对象要么就是函数指针。 接下来要说的东西可能以我之口舌没法很好的说清楚,重要的还是大家多理解代码,我觉得一码胜千言。要让objA和objB联系起来那么必然通过一种介质,假如这个函数为Connect,那么我们先将设会是下面这种形式: Connect(SIGANL(objA,xxxx),SLOT(objB,xxxx)); 这个形式模仿的是Qt的connect,这就不说了,我们现在来看看如果实现这个Connect函数,而他的参数又应该是什么?想一下,当我们操作objA到时候objB会得到相应的响应,那么这个函数里面我们至少要知道objA和objB关联的到底是那个函数,所以此处应该需要将函数传递到Connnect里面。ok,现在我们来看一个原型: template void Connect( const MString& funName, void(T::*fun1)(Args...), T* obj1, std::function ) 从这个原型我们几乎可以肯定这就是我们所需要的了,funName是函数的名字fun1就是objA要调用的函数,obj1就是objA,eventfun即为我们被触发的函数,从这个函数原型来看他是支持无限参数的,所以他所完成的事是Qt的信号槽亦或是boost的信号都做不到的,大家也许会像何以见得呢?当然更多的可能是怎么来调用这个函数?和上面一样我们可以再进一步封装然后如下调用: Connect(MSIGNAL(TestA, SetValue, &objA),MSLOT(&B::setValue,&objB));
这样一来是不是很清晰了呢?MSIGNAL包装的是类的名字,函数,对象的指针,MSLOT的参数为成员函数的地址以及对象指针,好吧,这就作为我们的调用约定,那么问题来了,MSIGNAL和MSLOT是如何实现的呢?为什么两个参数调用方式差距还如此之大呢?为了满足我们上面Connect的原型,SIGNAL必须能够生成一个和函数名等同的字符串以及该函数的函数指针和一个对象指针,而MSLOT将会通过成员函数的地址以及对象指针生成一个函数对象出来。 看到这里大家是不是觉得开始有些懵了呢?好吧,我们正在C++的路上越走越深,因为这些如果不是出于构建库的目的话正常使用过程中几乎可能肯定不会使用得到。 MSIGNAL和MSLOT的调用差别如此之大正是因为两者的实现方式天差地别,因为MSIGNAL的现实相当简单,其实他就是一个宏定义: #ifndef MSIGNAL #define MSIGNAL(className,memFun,obj) #memFun,&className::memFun,obj #endif 但是MSLOT的实现却相当复杂,要说清楚真心不是那么容易,代码虽然不多,但是想要理清还是需要好好琢磨一下: //=================================== // // 下面只是一些辅助函数,不需要看懂,只需要知道怎么使用MSLOT即可 // template struct MPropertyFunHelp{ template static inline auto Apply(T t,F fun,K obj, Args...args)-> decltype( MPropertyFunHelp ( t,fun, obj, std::get args...) ) { return MPropertyFunHelp ( t,fun,obj, std::get args... ); } }; template<> struct MPropertyFunHelp<0>{ template static inline auto Apply(T t, F fun,K obj,Args...args)->decltype( std::bind(fun,obj,args...) ) { return std::bind(fun,obj,args...); } }; template struct ToFun{ template static std::function auto __t = std::tuple_cat(t,std::make_tuple(std::_Ph return ToFun (__t,fun,obj); } }; template struct ToFun template static std::function return MPropertyFunHelp } }; // // 包装成员函数,我们需要用的就是下面的函数 // template std::function auto t = std::make_tuple(); return ToFun<0,sizeof...(Args),R>::Apply ( t,fun,obj ); } // // 包装自由函数 // template std::function return std::forward } //================================ 到此下面的函数便可被正确调用了: Connect(MSIGNAL(TestA, SetValue, &objA),MSLOT(&B::setValue,&objB));
该调用模式适用于任何类型包括自由函数而且不受参数个数限制都是同样的调用方式,也就是说无论SetValue和setValue的参数有多少个上面的函数调用依旧不变。 接下来大家关心的可能是Connect的实现了,只是今天就先到此为止吧,下一讲我们来讲如何实现Connect以及如果让这一切工作起来。 //============================================ |
|