根据字符串自动构造对应类
問題的起因是,我在做一個demo,有一個對象基類,以及一堆派生出的子對象,比如球體、立方體之類的對象。還有一個對象管理類,用于存儲場景中的所有對象。那么在初始化的時候,代碼是這么寫的:
class?objectInfo { private:vector<object*>?vecObjs; public:void?Init(){vector?name?={"Sphere","Cube","Cube","Cone",};for?(int?i?=?0;?i?<?name.size();?i ){object*?obj?=?nullptr;if?(name[i]?==?"Sphere"){obj?=?new?Sphere();}else?if?(name[i]?==?"Cube"){obj?=?new?Cube();}else?if?(name[i]?==?"Cone"){obj?=?new?Cone();}if?(obj){vecObjs.push_back(obj);}//?....}} };雖然是不會有什么問題,但感覺這么寫真的很蠢。所以開始研究自動做這件事的方法,也就是說,只要調一次Create()即可,不需要像上面這樣寫一長串的if / else,否則代碼會比較難維護。
現在,問題可以簡化為,給定一個字符串,自動構造出對應的對象。
C 目前已經支持了RTTI技術,在已有一個類的實例的時候,可以獲取類的名字(字符串形式的)。但這點似乎并不能派上用場,因為我們需要在得到實例之前去做從字符串到類的解析。據我所知,大部分C 框架都會定義很多宏,來完成代碼生成的過程,所以在使用這些框架的時候,每次定義一個類,都要加一堆奇奇怪怪的大寫字符。我用過的包含MFC,Qt和Unreal,但具體實現細節,我還沒有深究過。
依照代碼生成的思路,我以一個C 弱雞的角度,思考如果我自己去做這么一個自動生成器,應該怎么實現。(也就是我的做法大概率不是非常好的,如果希望實際使用的話,最好借鑒一些成熟的解決方案)
首先,要給每個類定義一個函數,比如對于類Sphere,應該有這么一個Create函數(當然,我們所有的討論是基于類有繼承關系,以下例子中類Sphere繼承自object):
object*?Create() {return?new?Sphere(); }這個函數也就是某個自動生成器最終調用的一個函數。首先它應該不能是類A成員函數,因為此時類A還沒有實例化,是訪問不到的。
為了根據字符串找到這個函數,我想到的是,可以存在一個字符串到函數的map里,它大概長這么個樣子:
map<string,?function<object*()>>也就是說,我們在定義一個類后,還需要完成如下的操作:
(1) 生成一個對應的 Create()函數。
(2) 把這個函數加入到map里。
如果讓宏來完成,那么Create函數的生成就是這樣的:
#define?CREATE(class_name)?\object*?Create()?{?return?new?class_name();};\根據以上思路,又引入了一個單例的Helper類(可以理解為Factory)來封裝一些東西,最終第一版是這樣的:
#include? #include? #include? #include? #include? using?namespace?std; class?object;#define?REGISTER(class_name)?\Helper::Inst()->Push(#class_name,[]()->object*?\{?return?new?class_name;});\class?Helper { private:map<string,?function<object*()>>?mapStr2Func;static?Helper*?helper;Helper()?{} public:static?Helper*?Inst(){if?(!helper){helper?=?new?Helper();}return?helper;}object*?Createobject(string?name){if?(mapStr2Func.find(name)?!=?mapStr2Func.end())return?mapStr2Func[name]();return?nullptr;}void?Push(string?name,?function<object*()>?func)?{?mapStr2Func[name]?=?func;?} };class?object { public:virtual?void?Print()?=?0;const?char*?GetClassName(){return?typeid(*this).name();} };class?Sphere?:?public?object {void?Print()?override?{?cout?<<?GetClassName()?<<?endl;?} };class?Cube?:?public?object {void?Print()?override?{?cout?<<?GetClassName()?<<?endl;?} };class?Cone?:?public?object {void?Print()?override?{?cout?<<?GetClassName()?<<?endl;?} };class?objectInfo { private:vector<object*>?vecObjs; public:void?Init(){vector?name?={"Sphere","Cube","Cube","Cone",};for?(int?i?=?0;?i?<?name.size();?i ){object*?obj?=?Helper::Inst()->Createobject(name[i]);if?(obj){vecObjs.push_back(obj);obj->Print();}??}} };Helper*?Helper::helper?=?nullptr; int?main() {REGISTER(Sphere)REGISTER(Cube)REGISTER(Cone)objectInfo?obj;obj.Init();system("pause"); }以上代碼最終打印的結果為:
但這個代碼有一處我非常不滿意的地方,這個REGISTER宏只能放在函數體里,比如這里的main里,而我希望的是能夠放在類聲明的旁邊。因為自動生成的代碼包含了把函數放入map的過程,而這樣的操作在全局空間中是不允許的,我們最多只能在全局空間寫一些變量的聲明加初始化,比如:
int?x?=?0;但無法做:
int?x?; x?=?0;?//?forbidden!糾結這個宏所在位置的原因在于:類的聲明和宏放在一起易于維護。
就我個人經驗而言,如果項目中有這么一個類base,我想繼承它做一個新的功能類,那么我一定會先參照它已有的另一個子類的代碼,看它是如何寫的,或者更直接的,我會把它復制過來,把不必要的東西刪掉,留下必要的。如果宏和類的聲明放在一起,那么這個東西我是不會落下的,照葫蘆畫瓢改一遍都不會出錯。但作為新手而言,我肯定很難想到,我要到另外一個看起來毫不相干的類里,添加一句宏。
作為改進,為了讓以上操作(map的insert操作),能順利在全局空間執行,我又構造了一個類Generator,利用它的構造過程偷偷完成了這一過程,也就是我可以在全局空間寫:
Generator*?generator?=?new?Generator();然后把那一堆邏輯放在Generator的構造函數里。反正以上行為就是穿了個馬甲。
在頭文件中定義變量其實是不符合規范的,為了穩妥可以把這個宏挪到對應的實現文件里,但出于個人強迫癥,我希望只在頭文件聲明就足以。我用的vs 2017竟然可以編譯過,但不確定其它編譯器是否可行。(難道這是msvc編譯器的特性?)
最終的代碼如下:
reflect.h
#pragma?once class?object;#include? using?namespace?std; #define?REGISTER(class_name)?\class?class_name##Generator?:?public?Generator{\public:class_name##Generator()?{?\Helper::Inst()->Push(#class_name,?this);}\object*?Create()?{return?new?class_name();}};\class_name##Generator*?class_name##Inst?=?new?class_name##Generator();class?Generator { public:virtual?object*?Create()?=?0; };class?Helper { private:map?mapStr2Generator;static?Helper*?helper;Helper()?{} public:static?Helper*?Inst(){if?(!helper){helper?=?new?Helper();}return?helper;}object*?Createobject(string?name){if?(mapStr2Generator.find(name)?!=?mapStr2Generator.end())return?mapStr2Generator[name]->Create();return?nullptr;}void?Push(string?name,?Generator*?generator)?{?mapStr2Generator[name]?=?generator;?} };test.h
#pragma?once#include? #include? #include? #include? #include?"reflect.h"class?object { public:virtual?void?Print()?=?0;const?char*?GetClassName(){return?typeid(*this).name();} };class?Sphere?:?public?object {void?Print()?override?{?cout?<<?GetClassName()?<<?endl;?} }; REGISTER(Sphere)class?Cube?:?public?object {void?Print()?override?{?cout?<<?GetClassName()?<<?endl;?} }; REGISTER(Cube)總結
以上是生活随笔為你收集整理的根据字符串自动构造对应类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑主机主板没电会怎么样(主机主板没电了
- 下一篇: linux查看文件内容命令vim(lin