C++反射机制之可变参数模板怎么实现C++反射

发布时间:2021-10-23 15:33:49 作者:iii
来源:亿速云 阅读:136

这篇文章主要介绍“C++反射机制之可变参数模板怎么实现C++反射”,在日常操作中,相信很多人在C++反射机制之可变参数模板怎么实现C++反射问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++反射机制之可变参数模板怎么实现C++反射”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

1. 概要

  2018年Bwar发布了《C++反射机制:可变参数模板实现C++反射》,文章非常实用,Bwar也见过好几个看了那篇文章后以同样方法实现反射的项目,也见过不少从我的文章抄过去连代码风格类名函数变量名什么都没改或者只是简单改一下重新发表的。被抄说明有价值,分享出来就不在意被抄,觉得文章有用就star Nebula吧,谢谢。那些用了可变参数模板实现反射的项目或文章大都是通过这种方法实现无参数版本的类对象构建,无参版本不能充分体现可变参数模板实现反射的真正价值。上篇文章中关于Targ...模板参数的说明不够详细且有些描述有问题,这次再写一篇这种反射实现的补充,重点说明容易出错的可变参数部分并纠正上篇的错误。毕竟在Nebula高性能网络框架中所有actor对象的创建都必须以反射方式创建,驾驭这种反射方式创建对象让Nebula的使用更轻松。

2. 引用折叠与类型推导

  可变参数模板主要通过T&&引用折叠及其类型推导实现的。关于引用折叠及类型推导的说明,网上可以找到大量资料,这里就不再赘述,推荐一篇言简意赅清晰明了的文章《图说函数模板右值引用参数(T&&)类型推导规则(C++11)》。

3. 回顾一下Nebula网络框架中的C++反射机制实现

  Nebula的Actor为事件(消息)处理者,所有业务逻辑均抽象成事件和事件处理,反射机制正是应用在Actor的动态创建上。Actor分为Cmd、Module、Step、Session四种不同类型。业务逻辑代码均通过从这四种不同类型时间处理者派生子类来实现,专注于业务逻辑实现,而无须关注业务逻辑之外的内容。Cmd和Module都是消息处理入库,业务开发人员定义了什么样的Cmd和Module对框架而言是未知的,因此这些Cmd和Module都配置在配置文件里,Nebula通过配置文件中的Cmd和Module的名称(字符串)完成它们的实例创建。通过反射机制动态创建Actor的关键代码如下:

Actor的类声明:

class Actor: public std::enable_shared_from_this<Actor>

Actor创建工厂:

template<typename ...Targs>
class ActorFactory
{
public:
    static ActorFactory* Instance()
    {
        if (nullptr == m_pActorFactory)
        {
            m_pActorFactory = new ActorFactory();
        }
        return(m_pActorFactory);
    }

    virtual ~ActorFactory(){};

    // 将“实例创建方法(DynamicCreator的CreateObject方法)”注册到ActorFactory,注册的同时赋予这个方法一个名字“类名”,后续可以通过“类名”获得该类的“实例创建方法”。这个实例创建方法实质上是个函数指针,在C++11里std::function的可读性比函数指针更好,所以用了std::function。
    bool Regist(const std::string& strTypeName, std::function<Actor*(Targs&&... args)> pFunc);

    // 传入“类名”和参数创建类实例,方法内部通过“类名”从m_mapCreateFunction获得了对应的“实例创建方法(DynamicCreator的CreateObject方法)”完成实例创建操作。
    Actor* Create(const std::string& strTypeName, Targs&&... args);

private:
    ActorFactory(){};
    static ActorFactory<Targs...>* m_pActorFactory;
    std::unordered_map<std::string, std::function<Actor*(Targs&&...)> > m_mapCreateFunction;
};

template<typename ...Targs>
ActorFactory<Targs...>* ActorFactory<Targs...>::m_pActorFactory = nullptr;

template<typename ...Targs>
bool ActorFactory<Targs...>::Regist(const std::string& strTypeName, std::function<Actor*(Targs&&... args)> pFunc)
{
    if (nullptr == pFunc)
    {
        return (false);
    }
    bool bReg = m_mapCreateFunction.insert(
                    std::make_pair(strTypeName, pFunc)).second;
    return (bReg);
}

template<typename ...Targs>
Actor* ActorFactory<Targs...>::Create(const std::string& strTypeName, Targs&&... args)
{
    auto iter = m_mapCreateFunction.find(strTypeName);
    if (iter == m_mapCreateFunction.end())
    {
        return (nullptr);
    }
    else
    {
        return (iter->second(std::forward<Targs>(args)...));
    }
}

动态创建类:

template<typename T, typename...Targs>
class DynamicCreator
{
public:
    struct Register
    {
        Register()
        {
            char* szDemangleName = nullptr;
            std::string strTypeName;
#ifdef __GNUC__
            szDemangleName = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
#else
            // 注意:这里不同编译器typeid(T).name()返回的字符串不一样,需要针对编译器写对应的实现
            //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);
            }
            ActorFactory<Targs...>::Instance()->Regist(strTypeName, CreateObject);
        }
        inline void do_nothing()const { };
    };

    DynamicCreator()
    {
        m_oRegister.do_nothing();   // 这里的函数调用虽无实际内容,却是在调用动态创建函数前完成m_oRegister实例创建的关键
    }
    virtual ~DynamicCreator(){};

    // 动态创建实例的方法,所有Actor实例均通过此方法创建。这是个模板方法,实际上每个Actor的派生类都对应了自己的CreateObject方法。
    static T* CreateObject(Targs&&... args)
    {
        T* pT = nullptr;
        try
        {
            pT = new T(std::forward<Targs>(args)...);
        }
        catch(std::bad_alloc& e)
        {
            return(nullptr);
        }
        return(pT);
    }

private:
    static Register m_oRegister;
};

template<typename T, typename ...Targs>
typename DynamicCreator<T, Targs...>::Register DynamicCreator<T, Targs...>::m_oRegister;

  上面ActorFactory和DynamicCreator就是C++反射机制的全部实现。要完成实例的动态创建还需要类定义必须满足(模板)要求。下面看一个可以动态创建实例的CmdHello<a name="CmdHello"></a>类定义:

// 类定义需要使用多重继承。
// 第一重继承neb::Cmd是CmdHello的实际基类(neb::Cmd为Actor的派生类,Actor是什么在本节开始的描述中有说明);
// 第二重继承为通过类名动态创建实例的需要,与template<typename T, typename...Targs> class DynamicCreator定义对应着看就很容易明白第一个模板参数(CmdHello)为待动态创建的类名,其他参数为该类的构造函数参数。
// 如果参数为某个类型的引用,作为模板参数时应指定到类型。
// 如果参数为某个类型的指针,作为模板参数时需指定为类型的指针。
class CmdHello: public neb::Cmd, public neb::DynamicCreator<CmdHello, int32>
{
public:
    CmdHello(int32 iCmd);
    virtual ~CmdHello();

    virtual bool Init();
    virtual bool AnyMessage(
                    std::shared_ptr<neb::SocketChannel> pChannel,
                    const MsgHead& oMsgHead,
                    const MsgBody& oMsgBody);
};

注意:《C++反射机制:可变参数模板实现C++反射》上篇CmdHello注释的这两个比如是错误的,具体原理见下文第5第6项比如: 参数类型const std::string&只需在neb::DynamicCreator的模板参数里填std::string比如:参数类型const std::string则需在neb::DynamicCreator的模板参数里填std::string

  再看看上面的反射机制是怎么调用的:

template <typename ...Targs>
std::shared_ptr<Cmd> WorkerImpl::MakeSharedCmd(Actor* pCreator, const std::string& strCmdName, Targs&&... args)
{
    LOG4_TRACE("%s(CmdName \"%s\")", __FUNCTION__, strCmdName.c_str());
    Cmd* pCmd = dynamic_cast<Cmd*>(ActorFactory<Targs...>::Instance()->Create(strCmdName, std::forward<Targs>(args)...));
    if (nullptr == pCmd)
    {
        LOG4_ERROR("failed to make shared cmd \"%s\"", strCmdName.c_str());
        return(nullptr);
    }
    ...
}

  MakeSharedCmd()方法的调用:

MakeSharedCmd(nullptr, oCmdConf["cmd"][i]("class"), iCmd);

4. MakeSharedActor系列函数创建对象注意事项

  这个C++反射机制的应用容易出错的地方是:

  注意以上两点,基本就不会有什么问题。

5. 动态创建原理

  在一系列的动态创建使用案例中得出上面两条注意事项,再从代码中看动态创建的本质。

5.1 注册对象创建函数指针

  首先动态创建是通过调用DynamicCreator模板类里的static T* CreateObject(Targs&&... args)函数来完成的。DynamicCreator模板类在public neb::DynamicCreator<CmdHello, int32>会创建一个静态的实例:

template<typename T, typename ...Targs>
typename DynamicCreator<T, Targs...>::Register DynamicCreator<T, Targs...>::m_oRegister;

  创建这个静态实例实际上是为了 ActorFactory<Targs...>::Instance()->Regist(strTypeName, CreateObject); 注册一个函数指针到ActorFactory,这个函数指针就是后续通过反射动态创建类对象的实际执行者。CreateObject()函数里是调用new,传递的参数也是完美转发给类的构造函数,而构造函数调用的实参与形参是支持隐式类型转换的,所以继承DynamicCreator时的模板参数列表无需跟类构造函数的类型完全一致。

5.2 动态创建的实质

  MakeSharedActor系列函数被调用,从调用的MakeSharedActor()参数是完美转发的,没有实参类型与形参类型的区别,也就不存在类型转换。MakeSharedActor()里通过调用ActorFactory<Targs...>::Instance()->Create(strCmdName, std::forward<Targs>(args)...)完成创建,这个调用实际上就是由显式的两部分和隐含CreateObject构成:

6. 动态创建设计原则和技巧

  动态创建的参数设计的好坏直接涉及到后续动态创建是否成功和动态创建的效率(参数引用传递和值传递的差别),所以定一个设计原则很重要。

  比如构造函数需要传递一个int型参数,模板参数类型也设计为int,但调用方实际传递int&会更方便更好理解,这时可以将模板参数类型改成int&并保持构造函数参数不变(如果将构造函数参数也改成int&会让人误解构造函数会改变参数的值,改成const int&又会让调用方也改成const int&才能成功调用)。

  已定义的变量在作为实参传递时往往是一个T&类型,这在对象引用(比如const std::string&)时一般不会有问题,因为构造函数和模板参数通常会设计为const std::string&,但基础类型int、float等在构造函数和模板参数通常是以值传递的,这时候就涉及到上面举例的int&的情景,如果不想调整模板参数类型,还有一个小技巧是在传递的实参前面加上(int)、(float)做一个强转,强转后参数变成按值传递就可以调用到正确的创建函数。伪代码如下:

// class Test : public neb::Actor, public neb::DynamicCreator<Test, int&, int&, std::string&>
class Test : public neb::Actor, public neb::DynamicCreator<Test, int, int, std::string&>       // 注意模板参数类型std::string&,而构造函数的参数类型为const std::string&
{
public:
    Test(int iFrom, int iTo, const std::string& strName);
    ...
};

int main()
{
    int iFrom = 0;
    int iTo = 500;
    std::string strName = "latency";    // 若上面模板参数类型改为const std::string&,则这里需改成 const std::string strName = "latency";
    MakeSharedActor("Test", iFrom, iTo, strName);    // 调用失败
    MakeSharedActor("Test", (int)iFrom, (int)iTo, strName);     // 调用成功
}

到此,关于“C++反射机制之可变参数模板怎么实现C++反射”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. C++反射机制:可变参数模板实现C++反射
  2. Python面向对象之反射机制怎么使用

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

c++

上一篇:Git提交规范的方法有哪些

下一篇:如何在Windows 10上启用现在可以安全地关闭计算机

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》