第124讲 DirectX11Frame(4)

今天没加班,好吧,那我们继续,上一讲我们说了Connect的声明原型,这一讲我们来看如何实现这个函数,从参数中其实我们大概可以推断出Connect的实现应该和第三个参数有关,这里我们再来回顾一下上一讲我们的声明式:


template<class T,class…Args>

 

 


void Connect(

 

 

 

 

 

 

const MString&

funName,  

 

// 作为信号触发的函数名

 

 

 

 

 

 

void(T::*fun1)(Args…),  

// funName对应的函数指针

 

 

 

 

 

 

T* obj1,  

 

 

// 持有函数名为funName的对象

 

 

 

 

 

 

std::function<void(Args…)>eventfun  

// 函数对象

 

 

 

 

 

 


其实从注释里面几乎可以肯定重点在于obj1本身,因为前面三个参数都是相关联的,而第四个是一个function(关于function到底是什么我没记错的话好像我们在前面的章节中有说过),而第四个参数所带来的灵活性就是无节操的无限制。说到这里,那么问题来了,Connect要如何做才能将这些杂乱的东西联系在一起呢?

我们先设想一种模型:obj1存储一张表,这张表记录他自身函数和槽函数相关联的信息(可以想象一下虚函数的派发原理,当然我们这个可没那个复杂)。ok,现在剩下的就是如何实现这张表了,map似乎可以满足我们的需求,这个关系也很简单,只需要将函数名作为key,将槽函数作为value来储存。这实现起来还是相对简单的,但是缺点是不支持重载函数,如果是Qt的信号槽就不存在这个问题,但是前提是得声明各种各样的信号。

怎么声明这个map呢???向下面这么干么?


template<class R,class…Args>

using AnyFun = std::function<R(Args…)>;


std::map<MString,AnyFun> 

mPropretyChangedFunMap;


知道这为什么不行吗?因为AnyFun本身依然还是模板,依然还是需要进行实例化才能够使用,所以上面的声明是不对的,那么,既然不能这么干,那么是不是可以考虑另辟蹊径了呢?前提首先要明白一点关键的地方在AnyFun上面而不是map上面,使用map来储存这个关系是没问题(如果我们想要支持重载函数的话那么可能要考虑multmap了),嗯,std::map<MString,void*>是不是可以呢?但是当回调的时候如何还原到函数类型呢?好吧,我们这里就不纠结,至少到这一步已经离成功不远了。


在我的工具库里面有一个MAny的东西,他能够储存任意类型的对象同时还能够还原对象的原型,这就是我们想要的,boost里面也有一个any(话说当初写这个MAny的时候也参考了boost的any,之所以没有使用boost的any主要是想让自己的东西依赖更少),所以我们可以这样声明map:

std::map<MString,MAny> 

mPerpertChangedFunMap;


此处,距离成功80%。接下来要做的事就是将响应槽函数和函数名关联起来:



void BindPropertChangedFun(const MString&

funName,const MAny&

fun){

 

 

 

 

mPerpertChangedFunMap[funName] = fun;

 

 

}


该是时候实现Connect了。


template<class T,class…Args>

 

 

static
void Connect(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*fun1)(Args…),

 

 

 

 

 

 

T* obj1,

 

 

 

 

 

 

std::function<void(Args…)>eventfun

 

 

 

 

 

 

)

 

 

{

 

 

 

 

(obj->BindPropertChangedFun)(funName,eventfun);

 

 

}


 

 

从这个实现我们看出来那个函数指针似乎没什么用啊,也不知道当初脑子是怎么想的,好吧,先不管,好吧,现在我们已经将这种关系联系在一起,但是任务仅仅完成了三分之一。接下来要做的事就是我们如何让被关联的槽对象被执行。让完成这个过程那么应该有一个通知的动作,而这个通知的动作应该要知道是什么样的类型函数被调用,同时还要知道当前调用的函数的函数名,还能够传递参数,从这些信息中可以简单的推断出来这个通知函数的原型:


template<class T,class…Args>

 

 

void NotifyPropertyChanged(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*func)(Args…),

 

 

 

 

 

 

Args…agrs

 

 

 

 

 

 

);


这个函数的调用也相当的复杂,声明也挺吓人,好吧,我们简化一下调用操作,比如如下:


NotifyPropertyChanged<TestA, const MString&

>(

MPropertyNotifer(TestA, SetValue),

value);


 

 

这样一来不至于在参数里面输入一个字符串那么突兀的吓人,这样一来还能够在编译器对参数进行检测,那么MPropertyNotifer又是什么呢?应该想起来了,和MSIGNAL一样同样是一个宏定义:


#ifndef MPropertyNotifer

#define MPropertyNotifer(className,memFun)
#memFun,&

className::memFun

#endif


现在调用约定也ok了,剩下的也就是怎么实现这个调用过程了,可以想象一下,首先我们通过第一个参数查询map,从中找到对应的MAny对象,然后还原到原始类型,最后调用传递进来的参数执行任务。


思路理清了,接下来就是怎么实现:


//

// 通过函数名查找MAny

//

MAny GetPropertChangedFun(const MString &

funName) const{

if(mPerpertChangedFunMap.count(funName)){

 

 

 

 

MAny any = mPerpertChangedFunMap.at(funName);

 

 

 

 


return any;

 

 

}

 

 

else{

 

 

 

 


return MAny();

 

 

}

}

//

// 获取被执行的函数

//

template<class…Args>

 

 

std::function<void(Args…)>MGetPropertChangedFun(

 

 

 

 

 

 

const MString&

funName)

 

 

{

 

 

 

 

try{

 

 

 

 

 

 

typedef std::function<void(Args…)>
funType;

 

 

 

 

 

 

MAny Any = GetPropertChangedFun(funName);

 

 

 

 

 

 


return mj::any_cast<
funType>(Any);

 

 

 

 

}

 

 

 

 

catch(mj::bad_any_cast e){

 

 

 

 

 

 


return nullptr;

 

 

 

 

}

 

 

}


有了这两个辅助函数后我们就可以实现


template<class T,class…Args>

 

 


void NotifyPropertyChanged(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*func)(Args…),

 

 

 

 

 

 

Args…agrs

 

 

 

 

 

 

)

 

 

{

 

 

 

 

//

 

 

 

 

// 获取转发函数

 

 

 

 

//

 

 

 

 

std::function<void(Args…)>
fun =

 

 

 

 

 

 

 

 

MGetPropertChangedFun<Args…>(funName);

 

 

 

 

if(fun){

 

 

 

 

 

 

fun(agrs…);

 

 

 

 

}

 

 

}


到了这里就大功告成了,现在将这一部分封装到一个基类中——MProperty


class  

MProperty

{

 

 

friend class MDataBind;

 

 

template<class T>

 

 

friend class MConnect;

public:

 

 

MProperty();

 

 

virtual ~MProperty();


protected:

 

 

//

 

 

// 将属性被修改的这件事通知出去

 

 

// 第一参数为修改属性函数的名字,可以使用 MPropertyNotifer 来调用函数简化操作

 

 

// MPropertyNotifer 该宏会对函数名和函数进行参数打包

 

 

// eg:

 

 

// NotifyPropertyChanged(MPropertyNotifer(className,memFunName),args…)

 

 

//

 

 

template<class T,class…Args>

 

 

void NotifyPropertyChanged(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*func)(Args…),

 

 

 

 

 

 

Args…agrs

 

 

 

 

 

 

)

 

 

{

 

 

 

 

//

 

 

 

 

// 获取转发函数

 

 

 

 

//

 

 

 

 

std::function<void(Args…)>
fun =

 

 

 

 

 

 

 

 

MGetPropertChangedFun<Args…>(funName,this);

 

 

 

 

if(fun){

 

 

 

 

 

 

fun(agrs…);

 

 

 

 

}

 

 

}



 

 

//

 

 

// 就算是子类也禁止访问

 

 

//

private:


 

 

MAny GetPropertChangedFun(const MString &

funName) const;


 

 

template<class…Args>

 

 

void BindPropertChangedFun(const MString&

funName,const MAny&

fun){

 

 

 

 

mPerpertChangedFunMap[funName] = fun;

 

 

}


 

 

//

 

 

// 获取多参数函数

 

 

//

 

 

template<class…Args>

 

 

std::function<void(Args…)>MGetPropertChangedFun(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

const MProperty* obj)

 

 

{

 

 

 

 

if(obj == nullptr)

 

 

 

 

 

 

return nullptr;

 

 

 

 

try{

 

 

 

 

 

 

typedef std::function<void(Args…)>
funType;

 

 

 

 

 

 

MAny Any = obj->GetPropertChangedFun(funName);

 

 

 

 

 

 

return mj::any_cast<
funType>(Any);

 

 

 

 

}

 

 

 

 

catch(mj::bad_any_cast e){

 

 

 

 

 

 

return nullptr;

 

 

 

 

}

 

 

}


private:

 

 

//

 

 

// 触发事件的函数名,接收事件的函数

 

 

//

 

 

MMap<MString,MAny> 

mPerpertChangedFunMap;

};



Connect作为MDataBind的静态成员函数,MDataBind作为MProperty的友元类,所以他能够访问到MProperty的私有函数:


class MDataBind

{

public:

 

 

MDataBind(){ }

 

 

virtual ~MDataBind(){ }


 

 

//

 

 

// 两个类之间的关联

 

 

//

 

 

template<class T,class…Args>

 

 

static std::shared_ptr<MConnect<T>>Connect(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*fun1)(Args…),

 

 

 

 

 

 

T* obj1,

 

 

 

 

 

 

std::function<void(Args…)>eventfun

 

 

 

 

 

 

)

 

 

{

 

 

 

 

MAddPropertChangedFun(

 

 

 

 

 

 

 

 

 

 

funName,

 

 

 

 

 

 

 

 

 

 

fun1,

 

 

 

 

 

 

 

 

 

 

obj1,

 

 

 

 

 

 

 

 

 

 

eventfun);


 

 

 

 

std::shared_ptr<MConnect<T>>connectObj(new MConnect<T>(funName,obj1,eventfun));

 

 

 

 

return connectObj;

 

 

}



 

 

//

 

 

// 断开连接

 

 

//

 

 

template<class T,class…Args>

 

 

static
void DisConnect(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*fun1)(Args…),

 

 

 

 

 

 

T* obj1,

 

 

 

 

 

 

std::function<void(Args…)>eventfun

 

 

 

 

 

 

)

 

 

{

 

 

 

 

std::function<void(Args…)>
fun{nullptr };

 

 

 

 

(obj1->BindPropertChangedFun)(funName,fun);

 

 

}


private:

 

 

//

 

 

// 添加属性改变函数

 

 

//

 

 

template<class T,class …Args>

 

 

static
void MAddPropertChangedFun(const MString&

funName,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

void(T::*fun)(Args…),

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

T* obj,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

std::function<void(Args…)>eventfun)

 

 

{

 

 

 

 

if(obj == nullptr)

 

 

 

 

 

 

return;

 

 

 

 

(obj->BindPropertChangedFun)(funName,eventfun);

 

 

}


};



现在我们再来看看上一讲我们说到的两个类:


class A : public MObject{

public:


void SetValue(const MString&

value){

if (mValue == value)

return;

mValue = value;


NotifyPropertyChanged<A, const MString&

>(

MPropertyNotifer(A, SetValue),

value);

}

private:

MString mValue;

};


class B : public MObject{

public:


void setValue(const MString&

value){

if (mValue == value)

return;

mValue = value;


NotifyPropertyChanged<B, const MString&

>(

MPropertyNotifer(B, setValue),

value);

}

private:

MString mValue;

};


void TestAFun(const MString&

value){

MString str(“TestAFun “);

str <<value;

box::QueBox(str);

}


现在当执行下面程序时首先objA中的值被修改,接着通知objB修改,最后通知自由函数TestAFun

{

A objA;

B objB;

MDataBind::Connect(MSIGNAL(A, SetValue, &

objA),

MSLOT(&

B::setValue,&

objB));

MDataBind::Connect(MSIGNAL(B, setValue, &

objB),

MSLOT(TestAFun));

objA.SetValue(“Hello World”);

}


现在来说一下这个属性的一些用处,他可以用于MVC模型,由于历史原因所以功能单一的他并没有从代码库中被移除,而是在当前版本上稍作修改。

这个版本的不足之处想必大家都看到了,他不支持多播,只能一对一,至于双向绑定不是问题,只需要反向调一下就行,尽管一对一监听已经可以满足我们大部分的需求,但有时候多播还是需要的,另一个问题就是就是不安全性,也就是说当objB被销毁时,槽对象并没有被销魂,他依然能够得到执行,这时候就是未定义行为,新版本解决了这些问题,但是当前版本依然被使用。


下面这个这个table就是以前使用这个属性完成的:

第124讲  DirectX11Frame(4)
第124讲  DirectX11Frame(4)
第124讲  DirectX11Frame(4)


MTableWindow  

pTableWindow{nullptr };

// 视图

 

 

MTableData  

 

mTableData;

 

 

 

 

 

// 数据


然后两者之间相互绑定:

pTableWindow.BindTableData(&

mTableData);

 

 

mTableData.BindTableWindow(&

pTableWindow);


我们通过右键触发生成一些随机数来填充数据,然后界面会得到相应的更新,同时如果我们在街面上进行单元格修改,数据也会得到相应的更新。


int row = gen();

// 生成随机的整数

 

 

 

 

int col = gen();

// 生成随机的整数

 

 

 

 

mTableData.reSize(row,col);

// 修改数据大小,同时界面的表格也会相应的被修改

HArray<HArray<MString>>data;

 

 

 

 

for(int i=0;

i<row;

++i){

 

 

 

 

 

 

HArray<MString>v;

 

 

 

 

 

 

for(int j=0;

j<col;

++j){

 

 

 

 

 

 

 

 

v<<
fgen();

// 生成随机的浮点数

 

 

 

 

 

 

}

 

 

 

 

 

 

data<<v;

 

 

 

 

}

 

 

 

 

mTableData.setData(data);



ok,剩下的问题,如何解决我们上面所说的缺点。


//============================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第124讲 DirectX11Frame(4)

|

发表评论