C++反射实现—根据类名动态创建对象

我们在编写C++框架时,经常会涉及到一项基础技术,就是根据“一个动态库 + 一个类名称字符串“,动态的创建类对象。

这样做的好处是可以实现框架与业务代码的彻底解耦。框架不用关心业务侧的具体实现细节,只需要提供一个基类由业务方继承实现,然后业务方在配置文件中配置对应的动态库+类名称即可实现自动加载并运行。我们通常把这类功能称为classloader,今天就带大家一起来实现下。

一、实现原理

classloader一般包含shared_library动态库加载/卸载、object_factory动态对象创建工厂、class_register动态类注册,以及class_loader统一管理部分。整体关系如下图:

动态对象创建的关键是,框架不知道业务类的任何信息,所以需要业务侧在实现Derived类(即图中DemoComponent)之后,使用REGISTER_CLASS宏注册类以让框架知晓(注册时,宏需要自动为类生成一个Creater方法并注册给ObjectFactory,因此保存注册信息的ObjectFactory需要使用单例)。

框架持有每个业务组件类的Creater方法之后,在需要创建CreateObject时使用单例保存的业务类注册creater即可创建对应对象。

二、具体实现

1、shared_library动态库加载/卸载

这部分是最简单的,我们使用dlopen、dlclose来实现即可,简单的封装下,这里不赘述,我们重点讲解后续部分。

class SharedLibrary {
    ...
    bool Load(const std::string &libraryFile){
        ...
        // Load so 
        _libraryHandle = dlopen(libraryFile.c_str(), real_flag); 
        if (!_libraryHandle) { 
            const char *err = dlerror(); 
            LOG(ERROR) << "Library [" << libraryFile << "] load fail: " << std::string(err); 
            return false; 
        }
        ...
    }
    ...
    bool UnLoad(){ 
        ...
        // UnLoad so 
        if (_libraryHandle) { 
            LOG(INFO) << "UnLoad"; 
            dlclose(_libraryHandle); 
            _libraryHandle = nullptr;
        }
        ...
    }
    ...
}

2、object_factory对象创建工厂与class_register动态类注册

object_factory对象工厂是classloader的核心,决定了如何完全松耦合的实现动态类创建。class_register作为辅助,为object_factory的预先注册提供宏支持。

2.1 新手入门

提到动态创建对象,相信新手想到的最简单的方法通常如下:

void CreateObject(std::string ObjName)
{
    if(ObjName == “A”)
    {
        new A;
    }
    else if(ObjName == “B”)
    {
        new B;
    }
    ...
}

但是这里有个问题,框架编写时并不知道业务要起什么类名,框架编译时也没有这些类的so库,又如何在代码中提前编写new 类名的代码呢?况且业务代码一直在变,框架怎么可能跟着业务变化一直修改这些地方呢?显然这种方法是不可行的。

2.2 宏注册

为了解决这个问题,接下来很多同学会想到可以用宏来实现传入字符串,new出对应的类,这个思路很好,所以我们接下来实现了一个宏定义:

// 动态注册类creator
#define REGISTER_CLASS(ClassName)                                                            \
  ClassName *Create##ClassName()                                                             \
  {                                                                                          \
    return new ClassName;                                                                    \
  }                                                                                          \
  static bool g_reg_##ClassName = ObjectFactory::GetInstance()->Register(#Derived, Create##UniqueID);

上面这个宏很简单,首先是定义了一个new对象的函数,new一个传入类名的对象。接着定义了一个static g_reg_xx变量,这个变量被初始化的时候,会调用到ObjectFactory::Register来保存类名和类creater方法的关系。这里我们使用static GetInstance单例函数,确保在所有static g_reg_xx对象构造之前,ObjectFactory单例对象已经完成了初始化。这里的static g_reg_xx变量也可以使用struct构造函数来代替,目的都是为了在加载REGISTER_CLASS代码之后就能自动创建一个static对象,从而自动完成类注册动作。

接下来,我们要实现下Register逻辑,这里比较简单,实现一个ObjectFactory单例,把Register传入的类名和类creater方法保存下来备用即可:

class ObjectFactory{
        // 动态注册类
        using ObjCreator = std::function<Object *(void)>;
        bool Register(const std::string &className, ObjCreator objCreator)
        {
            LOG(INFO) << "Register " << className;
            _creatorMap.insert(std::make_pair(className, objCreator));
            LOG(INFO) << "_creatorMap size: " << _creatorMap.size();
            return true;
        }
        ...
}

写到这里,我们会发现objCreator作为Register的通用Creator参数,定义的返回值要想保持一致,需要一个类似Object的基础类。我们可以据此实现这个Object基类,并要求所有的动态类都继承它。但是问题来了,有些类为了方便在类函数中把类对象作为参数传给外部等原因,已经继承了enable_shared_from_this<xxx>等其他基类,而使用多继承则容易让代码逻辑变得比较复杂。

2.3 模板

为了解决上边的问题,我们又想到了一个解决方法,那就是使用模板来解决,这样我们就不需要预先知道业务类的基类了。我们对ObjectFactory进行下模板改造,并实现CreateObject方法:

template <typename Base>
class ObjectFactory {
    ...
    // 动态注册类
    using ObjCreator = std::function<Base *(void)>; 
    bool Register(const std::string &className, ObjCreator objCreator)
    {
        LOG(INFO) << "Register " << className;
        _creatorMap.insert(std::make_pair(className, objCreator)); 
        LOG(INFO) << "_creatorMap size: " << _creatorMap.size();
        return true;
    }
    ...
    // 动态创建类对象
    std::shared_ptr<Base> CreateObject(const std::string &className)
    {
        LOG(INFO) << "CreateObject " << className << " _creatorMap size: " << _creatorMap.size();
        auto it = _creatorMap.find(className);
        if (it == _creatorMap.end())
        {
            LOG(ERROR) << className << " class not registed!";
            return nullptr;
        }
        return std::shared_ptr<Base>(it->second());
    }
    ...
}

REGISTER_CLASS宏也需要同步调整下,增加基类和模板:

// 动态注册类creator(子类、基类)
#define REGISTER_CLASS(Derived, Base)                                                        \
  Derived *Create##Derived()                                                                 \
  {                                                                                          \
    return new Derived;                                                                      \
  }                                                                                          \
  static bool g_reg_##Derived = ObjectFactory<Base>::GetInstance()->Register(#Derived, Create##Derived);

在特定类型组件场景下,基类是固定的,为了方便业务方REGISTER,我们可以额外为这些组件单独定义宏,以减少REGISTER参数,例如:

#define REGISTER_COMPONENT(Derived)   \
     REGISTER_CLASS(Derived, ComponentBase)

通过模板化改造,我们的封装已经顺利实现了与业务侧的解耦,唯一需要业务侧仅需要在实现业务类的时候调用REGISTER_CLASS进行注册即可。由于是全局static,当业务的so动态库被加载时,即可自动完成注册动作。

3、class_loader统一管理

为了方便对动态加载的so库和创建的obj对象进行统一管理,我们需要有一个类似manager的类,作为classloader入口的同时,持有和管理相关资源。

class ClassLoader
{
    ...
    // load one library
    bool ClassLoader::LoadLibrary(const std::string &libraryFile)
    {
        if (!IsLoaded(libraryFile))
        {
            LOG(INFO) << "LoadLibrary " << libraryFile;
            _libloaderMap[libraryFile] = std::make_shared<SharedLibrary>(libraryFile);
        }
        return IsLoaded(libraryFile);
    }
    ...
    // create Base Object
    template <typename Base>
    std::shared_ptr<Base> CreateObject(const std::string &className)
    {
        return ObjectFactory<Base>::GetInstance()->CreateObject(className);
    }
    ...
    void ClassLoader::Release()
    {
        // unload all library
        LOG(INFO) << "Release All Library";
        for (auto loader : _libloaderMap)
        {
            UnloadLibrary(loader.first);
        }
    }
};

4、测试

我们实际测试下动态加载效果:

int main()
{
    // create obj
    std::string libraryFile("libdemo_component.so");
    std::string className("DemoComponent");

    ClassLoader classloader;
    // load so lib
    auto ret = classloader.LoadLibrary(libraryFile);
    std::cout << "LoadLibrary: " << ret << std::endl;

    // create object
    std::shared_ptr<ComponentBase> obj = classloader.CreateObject<ComponentBase>(className);
    obj->Init();

    return 0;
}
I0406 23:16:19.192689 31550 class_loader.cc:30 ClassLoader()
I0406 23:16:19.192725 31550 class_loader.cc:44 LoadLibrary libdemo_component.so
I0406 23:16:19.192752 31550 shared_library.cc:12 SharedLibrary() libdemo_component.so
I0406 23:16:19.192759 31550 shared_library.cc:24 Load libdemo_component.so
I0406 23:16:19.193063 31550 object_factory.h:36 ObjectFactory Instance is initializing
ObjectFactory() ObjectFactory_1680774079193100
I0406 23:16:19.193133 31550 object_factory.h:47 Register DemoComponent
I0406 23:16:19.193186 31550 object_factory.h:50 _creatorMap size: 1
I0406 23:16:19.193351 31550 object_factory.h:60 CreateObject DemoComponent _creatorMap size: 1
I0406 23:16:19.193377 31550 component_base.cc:25 ComponentBase()
I0406 23:16:19.193392 31550 demo_component.h:29 DemoComponent()
I0406 23:16:19.193405 31550 demo_component.cc:25 DemoComponent Init()
I0406 23:16:19.193413 31550 demo_component.h:33 ~DemoComponent()
I0406 23:16:19.193423 31550 component_base.cc:46 ~ComponentBase()
I0406 23:16:19.193445 31550 class_loader.cc:35 ~ClassLoader()
I0406 23:16:19.193454 31550 class_loader.cc:85 Release All Library
I0406 23:16:19.193462 31550 class_loader.cc:75 UnLoading Library libdemo_component.so
I0406 23:16:19.193472 31550 shared_library.cc:62 UnLoad
I0406 23:16:19.193552 31550 shared_library.cc:18 ~SharedLibrary()

 

三、总结

好了,截至到现在,我们已经基本实现了一个动态创建对象的classloader封装雏形。但也可以看到他还很不完善,比如不支持构造函数传参、不支持并行加载/对象创建等。如果你对此感兴趣请给我留言,我们一起来继续完善它。

 

yan 23.4.5

 

参考:

C++框架技术-根据类名动态创建类对象

C++反射实现动态对象创建

欢迎关注下方“非著名资深码农“公众号进行交流~

发表评论

邮箱地址不会被公开。