您好,登錄后才能下訂單哦!
原本打算先和大家說怎么在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 eventfun
)
從這個原型我們幾乎可以肯定這就是我們所需要的了,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::Apply
(
t,fun, obj,
std::get(t),
args...)
)
{
return MPropertyFunHelp::Apply
(
t,fun,obj,
std::get(t),
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 Apply(T t,F fun,K obj){
auto __t = std::tuple_cat(t,std::make_tuple(std::_Ph()));
return ToFun::Apply
(__t,fun,obj);
}
};
template
struct ToFun{
template
static std::function Apply(T t,F fun,K obj){
return MPropertyFunHelp::Apply(t,fun,obj);
}
};
//
// 包裝成員函數,我們需要用的就是下面的函數
//
template
std::function MSLOT(R(T::*fun)(Args...),T* obj){
auto t = std::make_tuple();
return ToFun<0,sizeof...(Args),R>::Apply
(
t,fun,obj
);
}
//
// 包裝自由函數
//
template
std::function MSLOT(R(*fun)(Args...)){
return std::forward>(fun);
}
//================================
到此下面的函數便可被正確調用了:
Connect(MSIGNAL(TestA, SetValue, &objA),MSLOT(&B::setValue,&objB));
該調用模式適用于任何類型包括自由函數而且不受參數個數限制都是同樣的調用方式,也就是說無論SetValue和setValue的參數有多少個上面的函數調用依舊不變。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。