C++反射機制:可變參數模板實現C++反射

1. 概要

  本文描述一個經過C++可變參數模板實現C++反射機制的方法。該方法很是實用,在Nebula高性能網絡框架中大量應用,實現了很是強大的動態加載動態建立功能。Nebula框架在Github的倉庫地址html

  C++11的新特性--可變模版參數(variadic templates)是C++11新增的最強大的特性之一,它對參數進行了高度泛化,它能表示0到任意個數、任意類型的參數。關於可變參數模板的原理和應用不是本文重點,不過經過本文中的例子也可充分了解可變參數模板是如何應用的。ios

  熟悉Java或C#的同窗應該都知道反射機制,不少有名的框架都用到了反射這種特性,簡單的理解就是隻根據類的名字(字符串)建立類的實例。 C++並無直接從語言上提供反射機制給咱們用,不過無所不能的C++能夠經過一些trick來實現反射。Bwar也是在開發Nebula框架的時候須要用到反射機制,在網上參考了一些資料結合本身對C++11可變參數模板的理解實現了C++反射。c++


2. C++11以前的模擬反射機制實現

  Nebula框架是一個高性能事件驅動通用網絡框架,框架自己無任何業務邏輯實現,卻爲快速實現業務提供了強大的功能、統一的接口。業務邏輯經過從Nebula的Actor類接口編譯成so動態庫,Nebula加載業務邏輯動態庫實現各類功能Server,開發人員只需專一於業務邏輯代碼編寫,網絡通訊、定時器、數據序列化反序列化、採用何種通訊協議等所有交由框架完成。git

  開發人員編寫的業務邏輯類從Nebula的基類派生,但各業務邏輯派生類對Nebula來講是徹底未知的,Nebula須要加載這些動態庫並建立動態庫中的類實例就須要用到反射機制。初版的Nebula及其前身Starship框架以C++03標準開發,未知類名、未知參數個數、未知參數類型、更未知實參的狀況下,Bwar沒想到一個有效的加載動態庫並建立類實例的方式。爲此,將全部業務邏輯入口類的構造函數都設計成無參構造函數。github

  Bwar在2015年未找到比較好的實現,本身想了一種較爲取巧的加載動態庫並建立類實例的方法(這還不是反射機制,只是實現了能夠或須要反射機制來實現的功能)。這個方法在Nebula的C++03版本中應用並實測經過,同時也是在一個穩定運行兩年多的IM底層框架Starship中應用。下面給出這種實現方法的代碼:網絡

CmdHello.hpp:框架

#ifdef __cplusplus
extern "C" {
#endif
// @brief 建立函數聲明
// @note 插件代碼編譯成so後存放到plugin目錄,框架加載動態庫後調用create()建立插件類實例。
neb::Cmd* create();
#ifdef __cplusplus
}
#endif

namespace im
{

class CmdHello: public neb::Cmd
{
public:
    CmdHello();
    virtual ~CmdHello();
    virtual bool AnyMessage();
};

} /* namespace im */

CmdHello.cpp:分佈式

#include "CmdHello.hpp"

#ifdef __cplusplus
extern "C" {
#endif
neb::Cmd* create()
{
    neb::Cmd* pCmd = new im::CmdHello();
    return(pCmd);
}
#ifdef __cplusplus
}
#endif

namespace im
{

CmdHello::CmdHello()
{
}

CmdHello::~CmdHello()
{
}

bool CmdHello::AnyMessage()
{
    std::cout << "CmdHello" << std::endl;
    return(true);
}

}

  實現的關鍵在於create()函數,雖然每一個動態庫都寫了create()函數,但動態庫加載時每一個create()函數的地址是不同的,經過不一樣地址調用到的函數也就是不同的,因此能夠建立不一樣的實例。下面給出動態庫加載和調用create()函數,建立類實例的代碼片斷:函數

void* pHandle = NULL;
    pHandle = dlopen(strSoPath.c_str(), RTLD_NOW);
    char* dlsym_error = dlerror();
    if (dlsym_error)
    {
        LOG4_FATAL("cannot load dynamic lib %s!" , dlsym_error);
        if (pHandle != NULL)
        {
            dlclose(pHandle);
        }
        return(pSo);
    }
    CreateCmd* pCreateCmd = (CreateCmd*)dlsym(pHandle, strSymbol.c_str());
    dlsym_error = dlerror();
    if (dlsym_error)
    {
        LOG4_FATAL("dlsym error %s!" , dlsym_error);
        dlclose(pHandle);
        return(pSo);
    }
    Cmd* pCmd = pCreateCmd();

  對應這動態庫加載代碼片斷的配置文件以下:性能

{"cmd":10001, "so_path":"plugins/CmdHello.so", "entrance_symbol":"create", "load":false, "version":1}

  這些代碼實現達到了加載動態庫並建立框架未知類實例的目的。不過沒有反射機制那麼靈活,用起來也稍顯麻煩,開發人員寫好業務邏輯類以後還須要實現一個對應的全局create()函數。

3. C++反射機制實現思考

  Bwar曾經用C++模板封裝過一個基於pthread的通用線程類。下面是這個線程模板類具備表明性的一個函數實現,對設計C++反射機制實現有必定的啓發:

template <typename T>
void CThread<T>::StartRoutine(void* para)
{
    T* pT;
    pT = (T*) para;
    pT->Run();
}

  與之類似,建立一個未知的類實例能夠經過new T()的方式來實現,若是是帶參數的構造函數,則能夠new T(T1, T2)來實現。那麼,經過類名建立類實例,創建"ClassName"與T的對應關係,或者創建"ClassName"與包含了new T()語句的函數的對應關係便可實現無參構造類的反射機制。考慮到new T(T1, T2)的參數個數和參數類型都未知,應用C++11的可變參數模板解決參數問題,即完成帶參構造類的反射機制。

4. Nebula網絡框架中的C++反射機制實現

  研究C++反射機制實現最重要是Nebula網絡框架中有極其重要的應用,而Nebula框架在實現並應用了反射機制以後代碼量精簡了10%左右,同時易用性也有了很大的提升,再考慮到應用反射機制後給基於Nebula的業務邏輯開發帶來的好處,能夠說反射機制是Nebula框架僅次於以C++14標準重寫的重大提高。

  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類定義(注意看代碼註釋):

// 類定義須要使用多重繼承。
// 第一重繼承neb::Cmd是CmdHello的實際基類(neb::Cmd爲Actor的派生類,Actor是什麼在本節開始的描述中有說明);
// 第二重繼承爲經過類名動態建立實例的須要,與template<typename T, typename...Targs> class DynamicCreator定義對應着看就很容易明白第一個模板參數(CmdHello)爲待動態建立的類名,其餘參數爲該類的構造函數參數。
// 若是參數爲某個類型的引用,做爲模板參數時應指定到類型。好比: 參數類型const std::string&只需在neb::DynamicCreator的模板參數裏填std::string
// 若是參數爲某個類型的指針,做爲模板參數時需指定爲類型的指針。好比:參數類型const std::string*則需在neb::DynamicCreator的模板參數裏填std::string*
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);
};

  再看看上面的反射機制是怎麼調用的:

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);

  至此經過C++可變參數模板實現C++反射機制實現已所有講完,相信讀到這裏已經有了必定的理解,這是Nebula框架的核心功能之一,已有很多基於Nebula的應用實踐,是可用於生產的C++反射實現。

  這個C++反射機制的應用容易出錯的地方是

  • 類定義class CmdHello: public neb::Cmd, public neb::DynamicCreator<CmdHello, int32>中的模板參數必定要與構造函數中的參數類型嚴格匹配(不明白的請再閱讀一遍CmdHello類定義)。
  • 調用建立方法的地方傳入的實參類型必須與形參類型嚴格匹配。不能有隱式的類型轉換,好比類構造函數的形參類型爲unsigned int,調用ActorFactory<Targs...>::Instance()->Create()時傳入的實參爲int或short或unsigned short或enum都會致使ActorFactory沒法找到對應的「實例建立方法」,從而致使不能經過類名正常建立實例。

  注意以上兩點,基本就不會有什麼問題了。

5. 一個可執行的例子

  上面爲了說明C++反射機制給出的代碼全都來自Nebula框架。最後再提供一個可執行的例子加深理解。

DynamicCreate.cpp:

#include <string>
#include <iostream>
#include <typeinfo>
#include <memory>
#include <unordered_map>
#include <cxxabi.h>

namespace neb
{

class Actor
{
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)
    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);
    }

    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;
};

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

template<typename T, typename ...Targs>
class DynamicCreator
{
public:
    struct Register
    {
        Register()
        {
            std::cout << "DynamicCreator.Register construct" << std::endl;
            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);
            }

            ActorFactory<Targs...>::Instance()->Regist(strTypeName, CreateObject);
        }
        inline void do_nothing()const { };
    };
    DynamicCreator()
    {
        std::cout << "DynamicCreator construct" << std::endl;
        m_oRegister.do_nothing();
    }
    virtual ~DynamicCreator(){m_oRegister.do_nothing();};

    static T* CreateObject(Targs&&... args)
    {
        std::cout << "static Actor* DynamicCreator::CreateObject(Targs... args)" << std::endl;
        return new T(std::forward<Targs>(args)...);
    }

    virtual void Say()
    {
        std::cout << "DynamicCreator say" << std::endl;
    }
    static Register m_oRegister;
};

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

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;
    }
};

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()
{
    //Actor* p1 = ActorFactory<std::string, int>::Instance()->Create(std::string("Cmd"), std::string("neb::Cmd"), 1001);
    //Actor* p3 = ActorFactory<>::Instance()->Create(std::string("Cmd"));
    neb::Worker W;
    neb::Actor* p1 = W.CreateActor(std::string("neb::Cmd"));
    p1->Say();
    //std::cout << abi::__cxa_demangle(typeid(Worker).name(), nullptr, nullptr, nullptr) << std::endl;
    std::cout << "----------------------------------------------------------------------" << std::endl;
    neb::Actor* p2 = W.CreateActor(std::string("neb::Step"), std::string("neb::Step"), 1002);
    p2->Say();
    return(0);
}

  Nebula框架是用C++14標準寫的,在Makefile中有預編譯選項,能夠用C++11標準編譯,但未徹底支持C++11所有標準的編譯器可能沒法編譯成功。實測g++ 4.8.5不支持可變參數模板,建議採用gcc 5.0之後的編譯器,最好用gcc 6,Nebula用的是gcc6.4。

  這裏給出的例子DynamicCreate.cpp能夠這樣編譯:

g++ -std=c++11 DynamicCreate.cpp -o DynamicCreate

  程序執行結果以下:

DynamicCreator.Register construct
static ActorFactory* Instance()
ActorFactory construct
bool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)
m_mapCreateFunction.size() =1
DynamicCreator.Register construct
static ActorFactory* Instance()
ActorFactory construct
bool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)
m_mapCreateFunction.size() =1
static ActorFactory* Instance()
Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)
static Actor* DynamicCreator::CreateObject(Targs... args)
Actor construct
DynamicCreator construct
Create Cmd
I am Cmd
----------------------------------------------------------------------
static ActorFactory* Instance()
Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)
static Actor* DynamicCreator::CreateObject(Targs... args)
Actor construct
DynamicCreator construct
Create Step neb::Step with seq 1002
I am Step

  完畢,週末花了6個小時才寫完,找了個合適的時間發佈出來。

  Nebula框架系列技術分享 之 《C++反射機制:可變參數模板實現C++反射》。 若是以爲這篇文章對你有用,若是以爲Nebula框架還能夠,幫忙到Nebula的Github碼雲給個star,謝謝。Nebula不只是一個框架,還提供了一系列基於這個框架的應用,目標是打造一個高性能分佈式服務集羣解決方案。


參考資料:

相關文章
相關標籤/搜索