什么是反射
从创建对象的角度上来看,狭义的说,比如有个 class A ,你能直接 new A() 来创建 对象。但是如果想根据字符串 “A” 来创建 class A 的对象,比如 使用 new “A” 的形式来创建 对象,甚至 “A” 是个变量。 str = “A” , new str.
这种把 class 作为变量,又能在运行时创建对象的机制,就叫做反射。
大部分的高级编程语言,先天是支持反射的。用 lua 举例
local AllTypes = {
Type1 = 1,
Type2 = 2,
Type3 = 3,
}
local typeClsHash = {}
typeClsHash[AllTypes.Type1] = Cls1
typeClsHash[AllTypes.Type2] = Cls2
typeClsHash[AllTypes.Type3] = Cls3
local theType = AllTypes.Type2
local cls = typeClsHash[theType]
local instance = cls:new()
instance:doSth()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
如上面的代码,可以动态的通过 theType 变量的值,来选择 new 哪个 class 的对象。这样代码写起来就比较灵活,省去了写一大堆丑陋的 switch case,if/else 。
c++ 代码示例
需求
游戏里面有很多UI,是 BaseMenu 的子类。包括MenuStageSelect, MenuHUD 等等。希望通过 MenuManager::pushMenu() 一个函数,写明 Menu 的类型,动态创建和现实对应的 Menu.
原理
由于c++ 语言先天不支持反射机制,因此这种动态 new 不同 class 的实例的机制,在实现起来,必须依赖于函数指针。
用一个 std::map<>来存储不同类型的Menu 的创建函数,key 是 menu类型,value 是创建的函数指针。
这样才能实现反射机制,动态创建对象。
实现
MenuManager.h 定义所有 BaseMenu子类的枚举
typedef enum
{
MENU_STAGE_SELECT,
MENU_HUD,
}MenuType;
- 1
- 2
- 3
- 4
- 5
MenuManager 定义一个 std::map 成员,用于存储哪个类型的 menu ,由哪个函数来负责创建。
// Registered menus
typedef std::function<BaseMenu*()> MenuCreateFuncType;
std::map<MenuType, MenuCreateFuncType> _registerMenus;
- 1
- 2
- 3
每个 Menu 都需要提供自己的创建函数。在对应的类里面,实现一个 static XXX* createInstance() 的方法。
例如 MenuHUD
在 MenuHUD.h 声明 static MenuHUD* createInstance() 函数
class MenuHUD : public BaseMenu
{
public:
static MenuHUD* createInstance();
- 1
- 2
- 3
- 4
在 MenuHUD.cpp 里实现它
MenuHUD* MenuHUD::createInstance()
{
return new MenuHUD();
}
- 1
- 2
- 3
- 4
每个 BaseMenu 的子类都需要实现这个静态函数。
在游戏初始化时,注册所有 Menu 的创建函数。
void MenuManager::registAllMenus()
{
_registerMenus[MenuType::MENU_STAGE_SELECT] = MenuStageSelect::createInstance;
_registerMenus[MenuType::MENU_HUD] = MenuHUD::createInstance;
}
- 1
- 2
- 3
- 4
- 5
这些准备工作做完之后,就可以在 MenuManager::pushMenu(MenuType menuType) 函数里,根据 Menu 类型,动态 new 出不同类型的 menu 的实例了。
void MenuManager::pushMenu(MenuType menuType)
{
BaseMenu* pInstance = _registerMenus[menuType]();
/*
Do other things...
*/
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
上面 map 的 key 是 Menu 的枚举。如果希望用 Menu 的类名做变量,只需要把 key 的 枚举,修改为 std::string ,在 registerAllMenus() 里面,用类名作 key 即可。