前面两讲我们大概的说了一下DirectX11编程的一些基础——向量和矩阵,但是还没有进入主题,那么从这一讲开始我们正式进入正题,当然,这个首先得说清楚些,不管怎么说C++我们已经说了两年了,相信大家的C++基础已经够扎实的,所以接下来要说的东西会很多,你们可以把他理解为一个基于DirectX11的一个Frame,所以这期间我们会从怎么设计这个Frame说起直到最后我们将这个Frame搭建起来。
既然这是一个基于DirectX11的Frame,那么相应的反射机制应该是要有的,因为对于DirectX编程都是基于顶点,所以如果不具有反射机制使用硬编码的方式设计应用程序太不灵活,其次外观的控制我们想要达到的效果是只要一个美工就能够搞定一个华丽的界面。所以无论如何反射是必不可少的了。
C++不像java或者C#具有天生反射机制,所以这个工作自然落在了我们的头上。不过这不是什么难事,只是首先听起来觉得不是那么简单,但是我们不去尝试又怎么知道真的不简单呢?
关于实现反射机制的方式就我而言就有两种,一种是通过无尽的if……else……来得到自己想要的类型,这样可行,在指定的范围内确实也是有效,甚至可以有更好的容错性,但是不管从什么角度出发他都不是一种明智的选择,因为这种扩展性实在太糟,而且局限性也很大,他必须要求所有的东西都继承自同一个基类,当然这种要求不过分,但是可以有更好的选择。
熟悉过MFC就应该知道MFC有几个宏,其中两个就是用来这种序列化的,所以大可参考MFC的实现机制完成我们的类反射机制。
/*******************************************************/
首先,我们定义一个函数指针,这个函数指针用于创建类的对象实例:
typedef void*(*MCreateInstanceFun)(void);
这是一类无参但返回指针的函数,所以我们只需要在我们的类中添加一个这种类型的函数,然后让该函数作为该类的静态函数以便消除this指针的影响即可,他的样子如下:
class A{
public:
static void* CreateInstance(){
return new A;
}
};
CreateInstance() 便是我们所需要的创建对象实例的函数,于是我们只需要将这个函数保存在某个地方,然后当我们需要该对象的时候直接调用该函数即可得到该对象的指针,当然,这里有一个问题存在,当new失败的时候会抛出异常std::bad_alloc异常,所以这里得注意些,但是这种情况通常是不会发生的。
那么,问题来了,我们把该函数放在什么地方呢?回想一下有什么东西是在程序进入main函数之前执行的呢?全局变量或者是全局静态变量,对于一个类来说,他的静态数据或者方法总是存在的,所以,我们大可将这些信息扔进类的静态对象里面保存起来。嗯,说是保存倒不如说是转存。看下面,下面我们来设计一个工厂类。
//—————————————————–
// 设计一个反射工厂类型
//—————————————————–
class MClassFactory{
protected:
MClassFactory(){ }
virtual ~MClassFactory(){ }
public:
void* GetInstanceByName(const MDx11String&
name);
void RegisterClass(const MDx11String&
name, MCreateInstanceFun __class);
static
void Destroy(MClassFactory* p);
static std::shared_ptr<MClassFactory>Instance();
private:
std::unordered_map<std::string,MCreateInstanceFun>
mClassMap;
std::vector<std::string>
mRegisterClassName;
};
这个反射工厂类是彻彻底底的单例类型,我们只能通过Instance构造函数构造出他的对象,对了,这里有智能指针的高级用法,可以参考一下。
mClassMap ,上面我们说转存其实就是将类的工厂函数转存到这里啦,他是以类名为key,工厂函数为值,所以当将类名作为字符串就能够创建出一个相应的对象了,如同:
A* ptr = (A*)MClassFactory::Instance()->GetInstanceByName("
A"
);
嗯,此时,他的威力还没有体现出来。现在我们假设我们要些一个Frame级别的东西,那么我们是不是应该让所有的东西都继承自一个共同的基类呢?这是必须的,如果要是没有打算这么做那么这种设计方式是值得推敲的。
嗯,现在我们可以来看看这个反射工厂是如何实现的,其实大家想必应该是清楚的了,他的实现相当的简单:
//———————————————
// 反射工厂类的实现
//———————————————
void* MClassFactory::GetInstanceByName(const MDx11String &
name)
{
if(name.empty())
return nullptr;
auto iter = mClassMap.find(name);
if(iter == mClassMap.end())
return nullptr;
return iter->second();
}
void MClassFactory::RegisterClass(const MDx11String &
name, MCreateInstanceFun __class)
{
if(name.empty())
return;
mClassMap[name] = __class;
}
void MClassFactory::Destroy(MClassFactory* p){
if (p){
p->~MClassFactory();
}
}
std::shared_ptr<MClassFactory>MClassFactory::Instance(){
static std::shared_ptr<MClassFactory>__data(new MClassFactory,Destroy);
return __data;
}
上面的Instance函数所使用的就是智能指针的高级用法,因为这个反射工厂类作为一个彻彻底底的单例类型,所以我们需要有一个定制的删除器,只有只能才能够访问得到该类的析构函数。
现在回头看看,机制我们已经说了,就是这么干的,现在的问题是如何将我们自己的类中的工厂信息转存到这里来,而且还有保证这个反射工厂类的对象在我们所需要的时候他一定是已经存在了的。当然可以大张旗鼓的干,但是这不是好点子,而且就目前我们设计的反射工厂这个信息来看似乎也不怎么满足,当然修改一下也是可以的了,但是这真不是什么好办法。
我们可以在创建一个类,而这个类只实用于动态创建反射工厂类,这已经足够了。
//—————————————————-
// 设计一个反射动态生成类
//—————————————————-
class MClassDynamic{
public:
MClassDynamic(const MDx11String&
name, MCreateInstanceFun __class);
};
可以说这是一个空类,所以他不会占多少空间,就一个字节,而他就只是为了保证我们的反射工厂类的实例永远存在。
MClassDynamic::MClassDynamic(const MDx11String &
name, MCreateInstanceFun __class)
{
MClassFactory::Instance()->RegisterClass(name,__class);
}
类的工厂信息的转存就是在这里完成的。
好吧,现在一切都有了,那么接下来我们就使用最简单的方法来将他们用在我们以后的类中,那就是一组宏:
//—————————————————-
// 宣告Class信息
// 在class的声明中使用
//—————————————————-
#define DECLARE_CLASS(className)
private:
std::string className##Name{#className } ;
static
MClassDynamic* pclassName;
public:
static void* CreateInstance(){
return new className;
}
virtual const std::string&
ClassName() const{
return className##Name;
}
virtual const std::string&
ClassName(){
return className##Name;
}
//—————————————————
// 针对 DECLARE_CLASS 的实现
//—————————————————
#define IMPLEMENT_CLASS(className)
MClassDynamic* className::pclassName =
new MClassDynamic(#className,
reinterpret_cast<MCreateInstanceFun>(&
className::CreateInstance));
现在我们就可以在我们的类中如下使用啦:
//====================================
// 头文件中的声明
//====================================
class A{
DECLARE_CLASS(A)
public:
A();
…..
};
//=================================
// cpp 中的实现
//=================================
IMPLEMENT_CLASS(A)
A::A(){
…
}
…….
我们就可以使用字符串创建对象了。
这个Frame的内容会很多,这一讲我们就暂时完成反射类,下一讲我们将这个反射类放进MObject类进行管理,MObject类是我们这个Frame的基类。
=======================================
回复D查看目录,回复数字查看相应章节。
原文始发于微信公众号(
C/C++的编程教室
):第121讲 DirectX11Frame(1)
|