内省:从字面上理解,凡是查看(类、函数等)内部情况的函数叫做内省函数。。
在Java编程中,会经常要用到反射,但是我想很多使用C++的人至今都没有想过这个问题。
C++是不支持通过类名称字符串”ClassXX”来生成对象的,也就是说我们可以使用ClassXX* object =new ClassXX; 来生成对象,
但是不能通过ClassXX* object=new "ClassXX"; 来生成对象。
反射是程序可以访问、检测和修改它本身状态或行为的一种能力。有点抽象,我的理解就是程序在运行的过程中,可以通过类名字符串称创建对象,并获取类中申明的成员变量和方法。
2.具体设计与实现
2.1设计思路
我的设计思路大致是这样的。
(1)为需要反射的类中定义一个创建该类对象的一个回调函数;
(2)设计一个工厂类,类中有一个std::map,用于保存类名和创建实例的回调函数。通过类工厂来动态创建类对象;
(3)程序开始运行时,将回调函数存入std::map(哈希表)里面,类名字做为map的key值;
实现流程如下图所示:
2.2具体实现
下面我来一步一步的讲解具体的实现方法。
第一步:定义一个函数指针类型,用于指向创建类实例的回调函数。
typedef void* (*PTRCreateObject)(void);
1
第二步:定义和实现一个工厂类,用于保存保存类名和创建类实例的回调函数。工厂类的作用仅仅是用来保存类名与创建类实例的回调函数,所以程序的整个证明周期内无需多个工厂类的实例,所以这里采用单例模式来涉及工厂类。
//工厂类的定义
class ClassFactory{
private:
map<string, PTRCreateObject> m_classMap ;
ClassFactory(){}; //构造函数私有化
public:
void* getClassByName(string className);
void registClass(string name, PTRCreateObject method) ;
static ClassFactory& getInstance() ;
};
//工厂类的实现
//@brief:获取工厂类的单个实例对象
ClassFactory& ClassFactory::getInstance(){
static ClassFactory sLo_factory;
return sLo_factory ;
}
//@brief:通过类名称字符串获取类的实例
void* ClassFactory::getClassByName(string className){
map<string, PTRCreateObject>::const_iterator iter;
iter = m_classMap.find(className) ;
if ( iter == m_classMap.end() )
return NULL ;
else
return iter->second() ;
}
//@brief:将给定的类名称字符串和对应的创建类对象的函数保存到map中
void ClassFactory::registClass(string name, PTRCreateObject method){
m_classMap.insert(pair<string, PTRCreateObject>(name, method)) ;
}
第三步: 这一步比较重要,也是最值得深究的一步,也是容易犯迷糊的地方,仔细看。将定义的类注册到工厂类中。也就是说将类名称字符串和创建类实例的回调函数保存到工厂类的map中。这里我们又需要完成两个工作,第一个是定义一个创建类实例的回调函数,第二个就是将类名称字符串和我们定义的回调函数保存到工厂类的map中。假设我们定义了一个TestClassA。
//test class A
class TestClassA{
public:
void m_print(){
cout<<"hello TestClassA"<<endl;
};
};
//@brief:创建类实例的回调函数
TestClassA* createObjTestClassA{
return new TestClassA;
}
好了,我们完了第一个工作,定义了一个创建类实例的回调函数。下面我们要思考一下如何将这个回调函数和对应的类名称字符串保存到工厂类的map中。我这里的一个做法是创建一个全局变量,在创建这个全局变量时,调用的构造函数内将回调函数和对应的类名称字符串保存到工厂类的map中。在这里,这个全局变量的类型我们定义为RegisterAction。
//注册动作类
class RegisterAction{
public:
RegisterAction(string className,PTRCreateObject ptrCreateFn){
ClassFactory::getInstance().registClass(className,ptrCreateFn);
}
};
有个这个注册动作类,我们在每个类定义完成之后,我们就创建一个全局的注册动作类的对象,通过注册动作类的构造函数将我们定义的类的名称和回调函数注册到工厂类的map中。可以在程序的任何一个源文件中创建注册动作类的对象,但是在这里,我们放在回调函数后面创建。后面你就知道为什么这么做了。创建一个注册动作类的对象如下:
RegisterAction g_creatorRegisterTestClassA("TestClassA",(PTRCreateObject)createObjTestClassA);
1
到这里,我们就完成将类名称和创建类实例的回调函数注册到工厂类的map。下面再以另外一个类TestClassB为例,重温一下上面的步骤:
//test class B
class TestClassB{
public:
void m_print(){
cout<<"hello TestClassB"<<endl;
};
};
//@brief:创建类实例的回调函数
TestClassB* createObjTestClassB{
return new TestClassB;
}
//注册动作类的全局实例
RegisterAction g_creatorRegisterTestClassB("TestClassB",(PTRCreateObject)createObjTestClassB);
聪明的你,有没有发现,如果我们再定义一个类C、D….,我们重复的在写大量相似度极高的代码。那么我们如何偷懒呢,让代码变得简洁,提高我们的编码效率。有时我们就应该偷懒,不是说这个世界是懒人们创造的么,当然这些懒人们都很聪明。那么我们如何偷懒呢,如果你想到了宏,恭喜,答对了。其实仔细一看,包括回调函数的定义和注册动作的类的变量的定义,每个类的代码除了类名外其它都是一模一样的,那么我们就可以用下面的宏来替代这些重复代码。
#define REGISTER(className) \
className* objectCreator##className(){ \
return new className; \
} \
RegisterAction g_creatorRegister##className( \
#className,(PTRCreateObject)objectCreator##className)
有了上面的宏,我们就可以在每个类后面简单的写一个REGISTER(ClassName) 就完成了注册的功能,是不是很方便快捷呢!!!
2.3测试
至此,我们就完成了C++反射的部分功能,为什么是部分功能,后面再另外说明。急不可耐,我们先来测试一下,是否解决了上面我们提到的问题:如何通过类的名称字符串来生成类的对象。测试代码如下:
#include <map>
#include <iostream>
#include <string>
using namespace std;
//test class
class TestClass{
public:
void m_print(){
cout<<"hello TestClass"<<endl;
};
};
REGISTER(TestClass);
int main(int argc,char* argv[]){
TestClass* ptrObj=(TestClass*)ClassFactory::getInstance().getClassByName("TestClass");
ptrObj->m_print();
}
程序编译运行输出:
2.4可能存在的疑问
看了上面的测试代码,大家可能会唏嘘不已,我们在通过类名称字符串创建类实例的时候,我们还是需要用到类名进行强制类型转换,有了类名称,我们何必还要处心积虑实现反射的功能呢,直接用类名创建实例不就行了么?
其实,上面实现的反射只是解决了本文最初提出的问题。
那么在实际的项目中,还有一种应用场景就是我们定义好了基类,给客户继承,但是我们并不知道客户继承基类后的类型名称。我们可以通过配置文件说明客户实现的具体类型名称,这样我们就可以通过类名称字符串来创建客户自定义类的实例了。
https://blog.csdn.net/q1007729991/article/details/56012253
1. 什么是反射
学过 Java 或 C# 的同学应该都知道“反射”机制,很多有名的框架都用到了反射这种特性。这是一种很牛逼的特性,简单的理解就是只根据类的名字就可以获取到该类的实例。有人会说,这不是多此一举吗?直接 new 一个出来不就行了吗?像下面这样:
class Person {
public:
virtual void show() = 0;
}
class Allen : public Person {
virtual void show() {
std::cout << "Hello, I'm Allen!" << std::endl;
}
}
Person *p = new Allen();
p->show();
可是有时候,你定义好接口 Person 后,你并不知道谁将会实现该接口,甚至不知道什么时候会实现它。所以此时你无法通过 new 操作符来实例化对象。【这就是插件,接口的框架】
比如未来的某个时候有人编写了一个类叫 Luffy,但是此时你不可能实例化 Luffy 这个类,所以你只能编写下面这种代码:
std::string className = /*从配置文件中读取*/
Person *p = getNewInstance<Person>(className);
你的程序可以从配置文件中读取到 "Luffy" 这个字符串保存到变量 className 中。接下来使用函数 getNewInstance 就可以获取到 Luffy 实例化的对象。