反射
反射的核心目的是根据一个类名来生成对应的实例。也就是**如何通过类名称字符串来生成类的对象。比如有一个类ClassA,那么如何通过类名称字符串ClassA创建类的对象呢?**比如
class Person {public: virtual void show() = 0;}
class Allen : public Person { virtual void show() { std::cout << "Hello, I'm Allen!" << std::endl; }}std::string className = /*从配置文件中读取*/Person *p = getNewInstance<Person>(className);但是C++不像java或者C#一样提供这种机制。但是我们可以通过工厂模式+单例模式+映射器+宏来实现一个反射
一般来说,实现反射需要如下几个组件(步骤):
- 我们的业务代码,也就是需要被反射的类。这个类通常继承自一个反射基类如
ReflectObject。- 这个步骤的目的是为抽象对象工厂类
ObjectFactory提供统一的接口
- 这个步骤的目的是为抽象对象工厂类
- 一个抽象对象工厂类
ObjectFactory - 具体对象工厂类
- 一般情况下使用宏
- 反射器
- 通常情况下,反射器可以是单例。
- 反射器的核心是建立字符串(标识符)到回调函数的映射。然后通过一个容器储存。
- 在我们即将展示的例子中,这个回调函数是一个具体对象工厂的指针。然后我们会通过这个具体对象工厂来创建具体对象。
下面我们看代码
- 反射基类
ReflectObject。所有实现反射的类都需要继承自它。
class ReflectObject {public: virtual ~ReflectObject(){}};- 具体的被反射类
class Person : public ReflectObject{public: Person(){ std::cout << "Person()" << std::endl; } virtual ~Person(){ std::cout << "~Person()" << std::endl; } virtual void show(){ std::cout << "Hello, I'm person" << std::endl; }};
class Miku : public Person{public: Miku(){ std::cout << "Miku()" << std::endl; } virtual ~Miku(){ std::cout << "~Miku()" << std::endl; } virtual void show(){ std::cout << "Hello, I'm Miku" << std::endl; }};
class Animal : public ReflectObject{public: Animal(){ std::cout << "Animal()" << std::endl; } virtual ~Animal(){ std::cout << "~Animal()" << std::endl; } virtual void bark(){ std::cout << "animal bark" << std::endl; }};- 抽象对象工厂
ObjectFactory
class ObjectFactory {public: ObjectFactory(){ std::cout << "ObjectFactory()" << std::endl; } virtual ~ObjectFactory(){ std::cout << "~ObjectFactory()" << std::endl; } virtual ReflectObject* newInstance() = 0; //提供统一的纯虚函数,返回反射基类的指针。也就是说,所有的具体对象工厂类都要通过这个函数返回具体对象并被转化为反射基类的指针};- 反射器Reflector
- 此处我们以单例模式呈现。
class Reflector{public: static Reflector& reflector(); //mayer’s单例模式经典方法 Reflector(const Reflector&) = delete; //禁止拷贝构造 Reflector& operator= (const Reflector&) = delete; //禁止拷贝赋值 void registerFactory(const std::string& className, ObjectFactory* function); //注册至映射map
ReflectObject* getNewInstance(const std::string& className); //在映射器内查找类名对应的对象工厂指针。然后通过该指针调用该对象工厂对应的创建实例的函数。此处是newInstanceprivate: Reflector(); //私有构造 ~Reflector(); //私有析构 std::map<std::string, ObjectFactory*> objectFactories; //映射map。key是类名,value是该类的对象工厂指针。};Reflector::Reflector(){ std::cout << "Reflector constructor" << std::endl;}Reflector::~Reflector(){ //析构函数中释放资源。在当前业务中,反射器的映射map负责保有所有的具体对象工厂指针。所以需要负责释放。 std::map<std::string, ObjectFactory*>::iterator it = objectFactories.begin(); for (; it != objectFactories.end();++it){ delete it->second; } objectFactories.clear();}
void Reflector::registerFactory(const std::string& className, ObjectFactory *of){ //注册函数。首先在map中查找是否有对应的键值,如果没有则创建一个 名称-对象工厂指针 的映射 std::map<std::string, ObjectFactory*>::iterator it = objectFactories.find(className); if (it != objectFactories.end()) { std::cout << "该类已经存在" << std::endl; } else { objectFactories[className] = of; }}ReflectObject* Reflector::getNewInstance(const std::string& className){ //通过类名获取对象工厂的指针,然后调用对象工厂的创建具体实例的方法并返回。 std::map<std::string, ObjectFactory*>::iterator it = objectFactories.find(className); if (it != objectFactories.end()) { ObjectFactory *of = it->second; return of->newInstance(); //如果找到了。那么调用这个对象工厂指针的创建具体实例的方法。 } return NULL;}Reflector& Reflector::reflector() { //经典的mayer‘s单例模式 std::cout <<"get reflector reference " << std::endl; static Reflector reflector; return reflector;}
/*这是个非常特殊的地方。我们有一个全局函数模板。外部调用的是这个函数。这个函数是我们的反射器的包装。传入一个具体被映射类名字,同时将其做为模板参数。该函数内部会先获取单例的反射器的实例,然后通过该实例调用内部的getNewInstance成员函数。由于调用返回的是ReflectObject*指针,但是通常我们等号左侧调用的时候会是当前类型或者是实际父类,所以需要强转一下。*/template<typename T, typename... Args>T* getNewInstance(const std::string& className) { return static_cast<T*>(Reflector::reflector().getNewInstance(className));}- 具体对象工厂
class ObjectFactory_Miku: public ObjectFactory{ //继承自反射基类 public: ObjectFactory_Miku(){ std::cout <<"ObjectFactory_Miku() " << std::endl; } ReflectObject* newInstance(){ //实现创建实例的接口。 return new Miku(); };};- 主函数
int main(){ Reflector::reflector().registerFactory("Miku", new ObjectFactory_Miku()); Person *allen = getNewInstance<Miku>("Miku"); allen->show(); delete allen; return 0;}/*get reflector reference 获取反射器实例Reflector constructor 反射器创建且只创建一次ObjectFactory() new的对象工厂类的父类部分ObjectFactory_Miku() new的对象工厂类子类部分---以上是第一行的注册部分------get reflector reference 第二行的getnewinstance内部调用的获取反射器实例。Person() 反射器实例调用的getnewinstance找到的对象工厂指针调用的newInstance导致的Miku类的父类部分创建Miku() //miku类本类创建Hello, I'm Miku //show 业务代码~Miku()~Person()~ObjectFactory()*/这时候我们发现,写对象工厂类和注册比较麻烦,我们可以用一个宏来实现
#define REFLECT(name)\class ObjectFactory_##name : public ObjectFactory{\public:\ ObjectFactory_##name(){ std::cout << "ObjectFactory_" << #name << "()" << std::endl; }\ virtual ~ObjectFactory_##name(){ std::cout << "~ObjectFactory_" << #name << "()" << std::endl; }\ ReflectObject* newInstance() {\ return new name(); \ }\}; \Reflector::reflector().registerFactory(#name, new ObjectFactory_##name());宏的部分语法我们在杂记4中提到过,这里不赘述
这样我们可以直接REFLECT(Animal)就完成了创建对象工厂和注册
也可以考虑使用一个额外的类和全局对象的方式替换最后一行
class Register_##name{\public:\ Register_##name(){\ Reflector::reflector().registerFactory(#name, new ObjectFactory_##name()); \ }\};\Register_##name register_##name;我目前不清楚有什么好处,可能和链接有关?因为全局对象的构造函数在main前执行。
上面的代码我们梳理了最基本的反射原理和实现,参考自这里。现在有一个问题。如果我的类需要使用有参构造函数怎么办?可以使用可变模板参数。但是导致复杂非常多。我找到了Nebula的一种实现方式,参考自这里,我们一起分析一下。
#include <string>#include <iostream>#include <typeinfo>#include <memory>#include <unordered_map>#include <functional>#include <cxxabi.h>
namespace neb{ class Actor{ //*理解为反射基类 reflectobject public: Actor(){std::cout << "Actor construct" << std::endl;} virtual ~Actor(){}; virtual void Say() { std::cout << "Actor" << std::endl; } };
template<typename ...Targs> class ActorFactory{ //*理解为反射器类。此处是单例模式 public: //typedef Actor* (*ActorCreateFunction)(); //std::function< Actor*(Targs...args) > pp;
static ActorFactory* Instance(){ //! 线程不安全的单例模式。获取反射器指针。 std::cout << "static ActorFactory* Instance()" << std::endl; if (nullptr == m_pActorFactory){ m_pActorFactory = new ActorFactory(); } return(m_pActorFactory); }
virtual ~ActorFactory(){};
//Lambda: static std::string ReadTypeName(const char * name)
//bool Regist(const std::string& strTypeName, ActorCreateFunction pFunc) //bool Regist(const std::string& strTypeName, std::function<Actor*()> pFunc) /*
&将“实例创建方法(对象工厂的CreateObject方法)”注册到ActorFactory,注册的同时赋予这个方法一个名字“类名”,后续可以通过“类名”获得该类的“实例创建方法”。 &这个实例创建方法实质上是个函数指针,在C++11里std::function的可读性比函数指针更好,所以用了std::function。 */ bool Regist(const std::string& strTypeName, std::function<Actor*(Targs&&... args)> pFunc){ std::cout << "bool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)" << std::endl; if (nullptr == pFunc){ return(false); } std::string strRealTypeName = strTypeName; //[&strTypeName, &strRealTypeName]{int iPos = strTypeName.rfind(' '); strRealTypeName = std::move(strTypeName.substr(iPos+1, strTypeName.length() - (iPos + 1)));};
bool bReg = m_mapCreateFunction.insert(std::make_pair(strRealTypeName, pFunc)).second; // 创建键值对 std::cout << "m_mapCreateFunction.size() =" << m_mapCreateFunction.size() << std::endl; return(bReg); }
//& 传入“类名”和参数创建类实例,方法内部通过“类名”从m_mapCreateFunction获得了对应的“实例创建方法(DynamicCreator的CreateObject方法)”完成实例创建操作。 Actor* Create(const std::string& strTypeName, Targs&&... args){ std::cout << "Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)" << std::endl; auto iter = m_mapCreateFunction.find(strTypeName); if (iter == m_mapCreateFunction.end()){ return(nullptr); } else{ //return(iter->second()); return(iter->second(std::forward<Targs>(args)...)); //把参数完美转发至实例创建方法。 } }
private: ActorFactory(){std::cout << "ActorFactory construct" << std::endl;}; //私有构造,经典的单例模式 static ActorFactory<Targs...>* m_pActorFactory; //静态的单例的本类指针 std::unordered_map<std::string, std::function<Actor*(Targs&&...)> > m_mapCreateFunction; //存贮映射的数据结构。第二个std::function对象统一存储返回类型是Actor*,入参可变的函数。 };
template<typename ...Targs> ActorFactory<Targs...>* ActorFactory<Targs...>::m_pActorFactory = nullptr; //静态的单例的本类指针的类外定义并初始化
template<typename T, typename ...Targs> class DynamicCreator{ //*理解为对象工厂 /* 我们写自己的业务类的时候,不仅要继承自反射基类,也要继承自这个类。 继承自这个类的第一个参数就是我们的类名。会通过mangled方法获取到这个字符串 然后把这个字符串和这个类的CreateObject函数放进去 */ public: struct Register{ Register(){ std::cout << "DynamicCreator.Register construct" << std::endl; std::puts(__PRETTY_FUNCTION__); char* szDemangleName = nullptr; std::string strTypeName; //这个就是类名 #ifdef __GNUC__ szDemangleName = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr); #else //in this format?: szDemangleName = typeid(T).name(); szDemangleName = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr); #endif if (nullptr != szDemangleName){ strTypeName = szDemangleName; free(szDemangleName); } std::cout << strTypeName << std::endl; ActorFactory<Targs...>::Instance()->Regist(strTypeName, CreateObject); //通过反射器类实例把类名和创建函数放进去。 } inline void do_nothing()const { }; //!注意这个do_nothing。 }; DynamicCreator(){ std::puts(__PRETTY_FUNCTION__); std::cout << "DynamicCreator construct" << std::endl; m_oRegister.do_nothing(); //!这里的目的是使用一次我们嵌套类的static对象。这样的话编译器不会优化掉嵌套类静态对象的构造。尤其是分离编译的时候,可能会发生链接错误。 } virtual ~DynamicCreator(){m_oRegister.do_nothing();}; //!这里我就不知道为什么了
static T* CreateObject(Targs&&... args){ //这个实例创建函数很有意思。别看它返回的是T*,但是由于我们所有的业务代码具体类都需要继承自反射基类。所以说T*是子类指针,而反射器存储的是Actor*父类指针。很自然的多态存储。 std::cout << "static Actor* DynamicCreator::CreateObject(Targs... args)" << std::endl; return new T(std::forward<Targs>(args)...); //一旦调用这个函数,就执行这个new。把传进来的参数再次完美转发至对象类的构造函数。 }
virtual void Say(){ std::cout << "DynamicCreator say" << std::endl; } static Register m_oRegister; //这里必须要是静态的。注意这是静态成员变量,不是静态局部变量。因为我们把注册的过程放到了Register的构造函数中。细节下面再说。 };
template<typename T, typename ...Targs> typename DynamicCreator<T, Targs...>::Register DynamicCreator<T, Targs...>::m_oRegister; //成员static变量的类外定义并默认初始化。这一行非常关键。注意这是静态成员变量,不是静态局部变量。
//下面开始是业务类。第一个是继承自反射基类,然后继承自对象工厂类。对象工厂类第一个模板参数是本类类型,然后依次是构造函数参数类型。 class Cmd: public Actor, public DynamicCreator<Cmd>{ public: Cmd(){std::cout << "Create Cmd " << std::endl;} virtual void Say(){ std::cout << "I am Cmd" << std::endl; } };
class Step: public Actor, DynamicCreator<Step, std::string, int>{ public: Step(const std::string& strType, int iSeq){std::cout << "Create Step " << strType << " with seq " << iSeq << std::endl;} virtual void Say(){ std::cout << "I am Step" << std::endl; } };
struct miku:public Actor, DynamicCreator<miku, int, int>{ miku(int x, int y){ std::cout <<"miku" << x << y << std::endl; } };
class Worker{ public: template<typename ...Targs> Actor* CreateActor(const std::string& strTypeName, Targs&&... args){ Actor* p = ActorFactory<Targs...>::Instance()->Create(strTypeName, std::forward<Targs>(args)...); return(p); } };
}
int main(){ neb::Worker W; std::cout << "----------------------------------------------------------------------" << std::endl; neb::Actor* p1 = W.CreateActor(std::string("neb::Cmd")); p1->Say(); std::cout << "----------------------------------------------------------------------" << std::endl; neb::Actor* p2 = W.CreateActor(std::string("neb::Step"), std::string("neb::Step"), 1002); p2->Say(); std::cout << "----------------------------------------------------------------------" << std::endl; neb::Actor* p3 = W.CreateActor(std::string("neb::miku"),2,3); p3->Say(); return(0);}有一些具体的代码内容已经在注释说过了。我们一起看一下执行结果
DynamicCreator.Register construct //静态成员变量类外定义导致的默认初始化。neb::DynamicCreator<T, Targs>::Register::Register() [with T = neb::Cmd; Targs = {}] //为了好看打出来的neb::Cmd //strTypeName也就是拿到的类名static ActorFactory* Instance()//拿到ActorFactory的实例。因为ActorFactory也是单例模式。ActorFactory construct //构造ActorFactory。因为不同的模板参数是不同的类。所以也是三次。bool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc) //注册m_mapCreateFunction.size() =1 //不知道干啥的。
DynamicCreator.Register constructneb::DynamicCreator<T, Targs>::Register::Register() [with T = neb::Step; Targs = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int}]neb::Stepstatic ActorFactory* Instance()ActorFactory constructbool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)m_mapCreateFunction.size() =1
DynamicCreator.Register constructneb::DynamicCreator<T, Targs>::Register::Register() [with T = neb::miku; Targs = {int, int}]neb::mikustatic ActorFactory* Instance()ActorFactory constructbool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)m_mapCreateFunction.size() =1上面这一部分是在执行main的第一行代码前执行的。为什么是这个顺序?
首先,我们代码的这一行
template<typename T, typename ...Targs> typename DynamicCreator<T, Targs...>::Register DynamicCreator<T, Targs...>::m_oRegister; //成员static变量的类外定义并且被默认初始化。这一行非常关键。注意这不是静态局部变量非常重要。首先,业务代码有三个类模板。分别继承了不同模板参数的DynamicCreator类模板。所以我们有三个不同模板参数的DynamicCreator。所以三个不同的类都有自己的m_oRegister。同时,m_oRegister不是静态局部变量,是静态成员变量。生存周期不同。所以说,我们在类外部的这个对静态成员变量的定义导致了其被默认初始化。所以我们看到有三个DynamicCreator内部的Register的默认构造函数被调用。
在Register的默认构造函数中,我们获得到了不同的T。然后通过这个T,获得到了特殊处理的类名。随后,由于ActorFactory也是类模板,所以它也是三份。自然也有三份不同的m_pActorFactory,所以也有三个ActorFactory的构造函数被调用。然后我们拿到了ActorFactory的实例,调用它的Regist注册,传入类名和一个函数CreateObject。这个函数很特殊,是一个静态的成员函数。别看它返回的是T*,但是由于我们所有的业务代码具体类都需要继承自反射基类。所以说T*是子类指针,而反射器存储的是一个返回值为Actor*(父类指针)的std::function对象。很自然的多态存储。这个函数一旦被调用,就会new。把传进来的参数再次完美转发至对象类的构造函数。
好了,下面开始看执行部分。
由于我们已经获取到了ActorFactory的实例,所以这次在调用中不再构造。然后调用Create函数。传入类名和构造函数参数。在这个函数中寻找对应类名的实例创建方法,然后完美转发参数至这个函数。进入到映射器存储的对应类的实例创建方法也就是CreateObject函数,new一个对应的对象。
这时候,我们刚刚才开始创建对象。随后,这个对象的第一基类也就是反射基类被创建。然后第二基类,也就是对象工厂基类被创建。然后是对象的子类部分。
----------------------------------------------------------------------static ActorFactory* Instance() //获取实例。由于上面已经构造过ActorFactory了,所以直接拿就可以。Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args) //正式调用createstatic Actor* DynamicCreator::CreateObject(Targs... args) //调用至映射器存储的对应类的CreateObject函数,new一个对应的对象Actor construct //对应对象的反射基类部分neb::DynamicCreator<T, Targs>::DynamicCreator() [with T = neb::Cmd; Targs = {}]//为了好看DynamicCreator construct //对应对象的DynamicCreator基类部分。注意这个和上面的不同。这里是基类的构造。上面执行前的是内部嵌套类静态Register对象的初始化导致的。Create Cmd //对象子类部分I am Cmd----------------------------------------------------------------------static ActorFactory* Instance()Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)static Actor* DynamicCreator::CreateObject(Targs... args)Actor constructneb::DynamicCreator<T, Targs>::DynamicCreator() [with T = neb::Step; Targs = {std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, int}]DynamicCreator constructCreate Step neb::Step with seq 1002I am Step----------------------------------------------------------------------static ActorFactory* Instance()Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)static Actor* DynamicCreator::CreateObject(Targs... args)Actor constructneb::DynamicCreator<T, Targs>::DynamicCreator() [with T = neb::miku; Targs = {int, int}]DynamicCreator constructmiku23Actor编译期静态反射,来自这里,看不懂。
动态链接的使用经验
运行时动态链接
- 首先拥有动态库源文件,如
chn_lang.cpp
#include <iostream>extern "C"{ //必须使用extern "C" void print_cur_language(){ std::cout << "chinese" << std::endl; }}- 编译源文件
g++ -c -fPIC chn_lang.cpp -o chn_lang.o- 链接为动态链接库
g++ -shared chn_lang.o -o libchn_lang.so- 编写主文件
#include <iostream>#include <dlfcn.h>int main() { void* handle = dlopen("./libchn_lang.so", RTLD_LAZY); if (!handle) { std::cout << "Failed to load the library: " << dlerror() << std::endl; return 1; } // 从动态链接库中获取函数指针 typedef void (*ExampleFunction)(); ExampleFunction exampleFunc = (ExampleFunction) dlsym(handle, "print_cur_language"); if (!exampleFunc) { std::cout << "Failed to load the function: " << dlerror() << std::endl; dlclose(handle); return 1; } // 调用函数 exampleFunc(); // 关闭动态链接库 dlclose(handle);}- 编译主文件
- 使用
g++编译器时,使用-ldl选项是为了链接libdl库,该库提供了对动态链接库(.so文件)的动态加载和符号解析的支持。
- 使用
g++ main.cpp -ldl -o main运行时动态链接的好处和坏处
好处是可以自由的加载和卸载所需的动态库。坏处是有些API,比如dlsym是采用字符串来进行符号查找,所以C++这种带mangling的就会很难受。一般会使用extern "C"来强制使用C风格的API,但是这样就不能使用函数重载了。
加载时动态链接
根据我目前的学习方式,由于dlsym是采用我们拿字符串来进行符号查找,所以C++这种带mangling的就会很难受。
- 首先拥有动态库源文件,如
chn_lang.cpp
#include <iostream>extern "C"{ //如果此处使用了extern "C", 则后面的头文件也需要使用extern "C"注明 void print_cur_language(){ std::cout << "chinese" << std::endl; }}- 编译源文件
g++ -c -fPIC chn_lang.cpp -o chn_lang.o- 链接为动态链接库
g++ -shared chn_lang.o -o libchn_lang.so- 编写头文件
#ifndef LANGUAGEHEADER_H#define LANGUAGEHEADER_H
extern "C" void print_cur_language(); //因为我们上面使用了extern "C",所以这里也需要#endif- 编写主文件
#include <iostream>#include "languageheader.h"int main(){ print_cur_language();}-
编译主文件并链接至动态链接库
-
Terminal window g++ main.cpp -l<library1> -l<library2> -o executable如g++ main.cpp -lchn_lang -o main -
如果在使用
-l指定库文件名称的时候,我们的动态链接库不在标准路径,则需要配置环境变量并添加-L指定库文件的目录路径:-
Terminal window export LD_LIBRARY_PATH=<path_to_library>:$LD_LIBRARY_PATH如export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH然后g++ main.cpp -L./ -lchn_lang -o main -
采用
-l指定库文件名称会帮助我们把动态链接库的前缀lib和后缀.so补齐,所以此处不需要再添加。 -
搜索路径可以是绝对路径或相对路径。如果是完整的绝对路径(例如
/path/to/library),则链接器将使用该路径作为库文件的准确位置。如果是相对路径(例如./libraries或../libs),则链接器将在当前工作目录下搜索库文件。
-
-
如果我们的库文件的名称并不是标准化,如
lib___.so形式,则可以在-l命令后添加冒号:。然后放入库文件的全名附带后缀-
Terminal window g++ main.cpp -L./ -l:somethingchn_lang.so -o main
-
-
如果此时我们有多个动态链接库文件,并且都有同名符号,则链接顺序和符号强度决定了具体调用的函数。比如:
g++ main2.cpp -L./ -lchn_lang -leng_lang -o main2g++ main2.cpp -L./ -leng_lang -lchn_lang -o main2第一种输出chinese而第二种输出english
具体的一些讨论可以参考这两篇文章:动态链接库的符号重名问题 和 强弱符号和强弱引用