1

反射的一种实例—通过名称创建对象

反射的基础知识

  • 反射指程序可以在运行时访问、检测、修改自身的状态或行为。一般的实现是将元数据(类型信息、方法名、方法地址等)进行记录,然后访问元数据并根据元数据进行进一步的处理。反射在动态语言(如python)中有良好的支持,在静态语言(如C/C++)中支持较少,故在静态语言中需要编写额外的代码实现上述流程,以实现反射。
  • 反射使用场景较为广泛,如解析配置文件(如根据配置文件创建对于的类。这就需要程序能够理解类名这种元数据,并根据该元数据进一步进行处理,即根据类名创建对应对象。因为配置文件内容是可变的,故在编码时是无法确定要实例化哪一个类,在程序运行的时候才能确定,所以无法在编码时就直接实例化该对象)、序列化和反序列化、自动化测试等。
  • C++作为一种静态语言,对反射的支持较少,如果需要实现较为复杂反射需要用户自行编写相应的代码。但是C++也对反射有一定的支持,比如RTTI机制(通过 typeid 运算符和 dynamic_cast 运算符可以在运行时获取对象的类型信息。这使得程序能够在运行时检查对象的类型,并根据需要进行类型转换和处理)。同时有些第三方库也支持反射,如QT。

反射的一种实例—通过名称创建对象

  • 通过名称创建对应的类或者调用对象的方法是一种常见的反射。这种反射能有效提高程序的灵活性,因为名称可以从多方面获取,如配置文件、输入框等。要实现这种功能,需要记录类名及对应的构造对象函数这些元数据,然后根据这些元数据构建对应的对象。
  • C++实现这类反射有种方法就是。通过map保存<名称,构造对象函数>形式的元数据。需要支持通过名称创建对象的需要将上述元数据保存在map中(这可以通过宏实现,即定义一个注册宏,然后在对应的类定义文件中使用这个宏,这样该类的相关元数据就保存在map中)。然后在需要的时候,比如读取到配置文件的内容后,查询该map并执行对应的构造函数得到对象。
  • 借助上述思路,我想实现一个根据配置文件的名称内容构造对应的具体工厂对象的功能,以达到借助配置文件提高我程序的灵活性的目的。

    • 创建Getobjectbyname类,该类维护一个上述形式的保存元数据的map,同时提供向该map插入数据(即注册类型),通过map返回对象的接口。同时,由于该map包含多种类型,故需要进行多次注册操作,且都是向该map中插入数据,为了数据的准确插入,可使用单例模式保证该map的唯一性。其代码如下

      class Getobjectbyname
      {
         //创建对象的函数的指针,以此构建map以实现根据名字返回对应的创建对象的函数
         typedef  Factor_protocol_base* (_stdcall *creatFunc)();
      public:
         //单例模式保证只有一个对象,static对象只初始化一次
         static Getobjectbyname& getInstance();
         //这是通过名字创建对象的实现函数
         Factor_protocol_base* createObject(QString name);
         //所有能被创建的对象必须被注册,即插入到map中
         //为了不在增加类型时多次调用,可借助宏的使用,使得
         //在增加类型时写上宏,就能自动被注册
         void myRegister(QString name,creatFunc func);
      private:
         //这里使用单例模式方式,保证只有一个由名字动态创建对象的实例对象,故构造、析构函数为私有的
         Getobjectbyname();
         ~Getobjectbyname();
         //c++的反射机制是由维护一个<名字:创建对象函数>的map实现的。
         //其中的static的使用需要理解
         std::map<QString,creatFunc> myreflectmap;
      };
      
      Getobjectbyname &Getobjectbyname::getInstance()
      {
         //将这个对象设置为static对象,使得其初始化在main之前
         //且保证这个对象在所有被创建对象实例化之前就被实例了
         static Getobjectbyname Instance;
         return Instance;
      }
      
      Factor_protocol_base *Getobjectbyname::createObject(QString name)
      {
         if(name.isEmpty()){
             qDebug()<<"The name of factor-protocol-XX is null";
         }
         if(myreflectmap.find(name)!=myreflectmap.end()){
             creatFunc func=myreflectmap[name];
             //返回创建对象函数的执行结果,即相应的对象实例
             return func();
         }
         return nullptr;
      }
      
      void Getobjectbyname::myRegister(QString name, Getobjectbyname::creatFunc func)
      {
         if(name.isEmpty()){
             qDebug()<<"The name of register func is null";
         }
         if(myreflectmap.find(name)==myreflectmap.end()){
             //qDebug()<<name.toUtf8()<<"Registe compeletely";
             myreflectmap[name]=func;
         }else{
             qDebug()<<"Do not been double registered!!";
         }
      }
      
    • 创建REGISTER_OBJECTGETBYNAME宏,通过宏可以实现类型的注册,同时使得代码较为简单。注册宏的大致功能为:定义构造对象函数,该函数一般会调用对象类的构造函数 ;调用Getobjectbyname类的插入map函数。其代码如下

      #define REGISTER_OBJECTGETBYNAME(name)\
      Factor_protocol_base* _stdcall Creat1_##name(){\
         return new name;\
      }\
      class Register##name{\
      public:\
         Register##name(){\
             Getobjectbyname::getInstance().myRegister(#name,Creat1_##name);\
         }\
      };\
      static Register##name temp_##name;
    • 定义好上述两个文件后的使用方法是,在需要支持通过名称创建对应对象的类的定义文件后面写入宏,如REGISTER_OBJECTGETBYNAME(Factor_protocol_register);,然后在得到名称后,调用Getobjectbyname的创建对象的函数得到对应的对象实例,如上述的createObject()函数。

xianghanfeng
6 声望1 粉丝