前些天看到同事分享了一篇文章《在C++实现反射》,并且应用到项目中,自己也正在学习C#,也有了解到反射相关的知识,找工作的时候也被问到反射方面的知识,于是有点兴趣想了解一下,然后跑了一下里面的代码,发现虽然程序跑起来了,并输出期望的结果,但是在调试过程中发现其实程序是错误的。
首先,
GetClassByName
这句的调用返回NULL,这个是bug的地方。通过打LOG发现,我想动态生成的类,并没有在ClassFactory中
m_clsMap注册,分析代码,发现其最直接的原因就是,最关键的、实现动态类自行注册的语句,并没有执行起来,也就是下面的语句:
template<class T,const char name[]>
const RegistyClass Register<T,name>::rc(name, Register<T, name>::CreateInstance);
这一句是Register类中的静态变量rc进行定义并初始化的语句,代码的本意是,动态类通过继承这个Register类,在程序开始运行的时候,也就是rc静态变量的初始化过程中,实现自身注册到ClassFactory中。
但是GetClassByName返回NULL,表明这一句并没有执行起来。自己检查了一下代码,也没找出哪里有明显的问题。于是向同事请教,同事表示他的代码运行正常,并且已经运用到项目当中,这让我更加不解,于是向同事借了代码来进行对比,对比之后发现同事和我的代码中最大的不同就是,他的动态类中有自己的构造函数,虽然是一个空的默认构造(因为我是直接复制原blog的代码,原blog的代码是没有构造函数的)。
为了验证一下自己的猜想,我在动态类中加入了一个空的默认构造函数,运行程序后,发现动态类可以实现自行注册到ClassFactory了,而且GetClassName返回的也是一个实际的地址,而不是NULL。
不过,这也让我有点不解,因为虽然我添加了一个默认的的构造函数,但是如果我先前的动态类的代码,什么构造函数都没有的话,正常来说,编译器应该也会帮我生成一个默认的构造函数,但是从问题的现象逆向分析起来,编译器偏偏就没有这样做。和同事说了一下问题的所在,同事同样表示不解。无奈,于是开始寻求Google/百度的帮助,在经过一番搜索之后,看了stackoverflow的一个回答,终于找到答案了。
简单整理了一下(部分内容有省略):根据C++标准,类模板的隐式实例化只会导致其static数据成员声明的实例化,不会促成其定义的实例化。
于是结果就明朗了,动态类继承Register,的确是隐式实例化了相应的Register类模式和其static数据成员的声明,但是相应的static静态数据成员,也就是rc静态变量的定义初始化模板没有进行隐式实例化,这个需要我们手动进行实例化。不过这个也不难,只要手动引用一下rc,促进其定义的模板实例化就行了。
有几种方法可以做到,其中一个方法就是在动态类中定义一个构造函数。因为构造函数的构造次序是,先构造基类再到派生类,要构造动态类就先要构造Register,所以在这里就会产生一个执行链:动态类的构造函数--->Register的构造函数---->const RegistyClass tmp=rc;,最后一步产生对rc变量的引用,强制编译器实例化rc的初始化模板,所以代码就正常了。
另外的更直接的方法就是,直接在动态类中编写对rc变量进行引用的代码,例如(void*)&rc;,这样,rc的初始化模板就会被强制实例化出来。
至于我的代码里面,为什么看上去感觉编译器没有帮我生成一个默认的构造函数,看了下《Effective C++》,其中条款05提到,“惟有当这些函数(构造、析构等)被需要(被调用)它们才会被编译器创建出来”。浏览下整个项目,因为代码中都没有哪个地方是明确告诉编译器需要调用动态类的构造函数,所以感觉因为优化方面的问题,编译器在背后就没有帮我生成了。不过这个纯属猜测,没有验证过。
唯一一个看上去“貌似”会调用动态类的构造函数的地方,就是Register类的CreateInstance方法中的new T;,但是恰恰这一句又是需要rc变量初始化模板的实例化,所以这里会产生一个循环引用的问题:rc变量初始化模板实例化-----(需要)--->动态类的构造函数-------(需要)-------->编译器为我实现一个默认构造函数--------(需要)------>出现调用动态类的构造函数的代码-----(需要)---->Creatinstance静态成员函数模板的实例化-------(需要)----->rc变量初始化模板实例化。不过这个纯属猜测,没有验证过。
另外,至于原先的代码,为什么GetClassByName返回NULL,并且通过NULL指针调用相应的成员函数,程序都没有崩溃呢?这里有解释:《浅析C++中的this指针 通过空指针(NULL)可以正确调用一些类的成员函数?》
此外,原先的代码中还有一些问题,比如ClassFactory的m_clsMap可以返回一个静态函数中的局部变量代替,以避免静态变更初始化顺序的问题。