c++實(shí)現(xiàn)反射

反射機(jī)制在java中是一個(gè)非常重要的特性,比如在spring框架中,就使用了xml + 反射來(lái)完成類的動(dòng)態(tài)擴(kuò)展。
簡(jiǎn)單來(lái)說(shuō),反射就是根據(jù)一個(gè)字符串查找到一個(gè)類,查找到一個(gè)函數(shù),并能生成這個(gè)類,或者函數(shù)的實(shí)例。換句話說(shuō),就是在運(yùn)行期通過(guò)字符串到內(nèi)存單元的反向映射。
在動(dòng)態(tài)語(yǔ)言中,反射是非常容易實(shí)現(xiàn)的,在java中,由于有字節(jié)碼作為中間層的存在,所以實(shí)現(xiàn)也并不復(fù)雜。但是對(duì)于c++來(lái)說(shuō),靜態(tài)編譯的程序在運(yùn)行期能打交道的,只是一些內(nèi)存地址,沒(méi)有任何字符串相關(guān)的東西,也就是說(shuō),你寫程序時(shí)的那些變量名,真正編譯結(jié)束后,只是一些內(nèi)存地址。
當(dāng)然了,萬(wàn)事沒(méi)有絕對(duì)。 要想在c++中實(shí)現(xiàn)類似java的反射功能,也是可以做到的,比如——增加中間層。
unix設(shè)計(jì)藝術(shù)里曾說(shuō)過(guò):所有的問(wèn)題都可以通過(guò)增加中間層的方式來(lái)解決。
增加中間層也分兩種,下面先說(shuō)第一種:
1 編譯期增加中間層
我們知道,程序在編譯期,是有一個(gè)全局的符號(hào)對(duì)照表的,這個(gè)里面保存了字符串(變量或者類名)到內(nèi)存地址的映射,但是一旦編譯完成,在鏈接時(shí),這些“多余”的信息都將不存在。因此只需要找到一個(gè)方式,能把這些信息保存下來(lái),就可以了。順著這個(gè)思路想下去,就會(huì)發(fā)現(xiàn),我們只要把靜態(tài)鏈接改為動(dòng)態(tài)鏈接,就可以將需要的符號(hào)信息保存下來(lái)。
具體來(lái)說(shuō):
(1) 將所有需要?jiǎng)討B(tài)創(chuàng)建的類放置于一個(gè)或多個(gè)獨(dú)立的文件中;
(2) 將這些文件編譯為一個(gè).so文件;
(3) 在主程序中通過(guò)dlopen打開(kāi)so文件;
(4) 調(diào)用dlsym(*pvSo, "create")獲得需要的函數(shù);
在dlsym中,完成了從字符串到函數(shù)的映射。
這種方式其實(shí)很不錯(cuò)的,但是,對(duì)于c++來(lái)說(shuō),函數(shù)的命名方式和c完全不同,所以需要通過(guò)各種extern來(lái)修飾函數(shù)。而且,由于使用了動(dòng)態(tài)編譯,在so中就不能再使用一些模板的特性了。當(dāng)然了,還有一些莫名其妙的很奇葩的坑,就不一一細(xì)說(shuō)了。
簡(jiǎn)單的示例代碼如下:

//test.so
class Test                                                                                               
{                                                                                                        
    public:                                                                                              
    void fun() {}                                                                                        
};                                                                                                       
                                                                                                         
extern "C" Test* create()                                                                                
{                                                                                                        
    return new CheckBankAtm;                                                                             
}                                                                                                        
                                                                                                         
//main
int main()                                                                                               
{                                                                                                        
    void* so = dlopen("test.so", RTLD_LAZY);                                                             
    Test* (*fun)();                                                                                      
                                                                                                         
    fun = (Test* (*) ()) dlsym(so, "create");                                                            
}                

2 運(yùn)行期增加中間層
由于動(dòng)態(tài)編譯存在各種各樣的問(wèn)題,所以我們還是更傾向于選擇靜態(tài)編譯。也就是說(shuō)在運(yùn)行期增加一個(gè)字符串到函數(shù)的中間層。我們需要準(zhǔn)備一個(gè)全局的map,以字符串為key,函數(shù)指針作為value。同時(shí)提供一個(gè)注冊(cè)機(jī)制,把每個(gè)需要的類注冊(cè)到這個(gè)全局map中。這里還剩下最后一個(gè)問(wèn)題,也是最重要的問(wèn)題:誰(shuí)來(lái)完成這個(gè)注冊(cè)的過(guò)程?
如果是調(diào)用者來(lái)注冊(cè),那么首先,我們需要在調(diào)用的文件中include用到的所有類的頭文件,之后,我們需要一個(gè)一個(gè)把類注冊(cè)進(jìn)來(lái),如果需要注冊(cè)的類有上千個(gè),這個(gè)過(guò)程將會(huì)非常的繁雜。
如果是類的作者自己完成注冊(cè),就涉及到另一個(gè)問(wèn)題,如何注冊(cè)?因?yàn)樽?cè)的這個(gè)過(guò)程,本身需要在類外面,而且還必須是一段可執(zhí)行代碼。這樣就又繞回了上面那步,誰(shuí)來(lái)調(diào)用注冊(cè)代碼?
可以通過(guò)一個(gè)比較取巧的辦法:類的靜態(tài)對(duì)象。
我們知道,類的靜態(tài)對(duì)象是全局的,而所有的全局變量,編譯器保證了一定會(huì)在main執(zhí)行前被初始化,換句話說(shuō),我們只要把注冊(cè)代碼放置于類的靜態(tài)變量的初始化過(guò)程中,就可以了。
示例代碼如下:

#include <iostream>
#include <string>
#include <map>

using namespace std;

#define BASE_CLASS Test
#define GLOBAL_FUN_MAP FunMap<BASE_CLASS>::get_fun_map()
#define DEFINE_CLASS(class_name, fun_name) \
    class_name(std::string) \
    {\
        GLOBAL_FUN_MAP.regist(#fun_name, class_name::fun_name);\
    }\
    class_name(){}\
    static class_name class_name##_;\
    static BASE_CLASS* fun_name()\
    {\
        return new class_name;\
    }
#define REGIST_CLASS(class_name) \
    class_name class_name::class_name##_(#class_name);

template <class T>
class FunMap
{
    typedef T* (*FUN)(void);
    map<std::string, FUN> fun_map_;
    public:
    void regist(string fun_name, FUN fun)
    {
        fun_map_[fun_name] = fun;  
    }

    T* get(const string fun_name)
    {
        if (fun_map_.end() != fun_map_.find(fun_name))
        {
            return fun_map_[fun_name](); 
        }
        else
        {
            return NULL;
        }
    }

    static FunMap<T>& get_fun_map()
    {
        static FunMap<T> fun_map;
        return fun_map;
    }
};

class Test
{};

class Test1 : public Test
{
    public:
    DEFINE_CLASS(Test1, test1)
};

class Test2 : public Test
{
    public:
    DEFINE_CLASS(Test2, test2)
};

REGIST_CLASS(Test1)
REGIST_CLASS(Test2)

int main()
{
    GLOBAL_FUN_MAP.get("test1");   
    GLOBAL_FUN_MAP.get("test2");   
    GLOBAL_FUN_MAP.get("11111111");
}

利用了多態(tài)和模板增加了程序的靈活性,同時(shí)使用了宏來(lái)簡(jiǎn)化代碼。
使用時(shí),需要在類中增加如下代碼:

DEFINE_CLASS(Test1, test1)  //第一個(gè)參數(shù)為類名,第二個(gè)參數(shù)為字符串名

同時(shí),需要在類的實(shí)現(xiàn)中增加:

REGIST_CLASS(Test1) //參數(shù)為類名
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 幾種語(yǔ)言的特性 匯編程序:將匯編語(yǔ)言源程序翻譯成目標(biāo)程序編譯程序:將高級(jí)語(yǔ)言源程序翻譯成目標(biāo)程序解釋程序:將高級(jí)語(yǔ)...
    囊螢映雪的螢閱讀 2,939評(píng)論 1 5
  • 1.import static是Java 5增加的功能,就是將Import類中的靜態(tài)方法,可以作為本類的靜態(tài)方法來(lái)...
    XLsn0w閱讀 1,262評(píng)論 0 2
  • 江城秋來(lái)晚 寒風(fēng)夜輕襲 夢(mèng)回江北地 兒幼雙親老
    錢炎閱讀 145評(píng)論 0 4
  • 種下的好種子 1.通過(guò)放松冥想,借助水晶球的能力,幫助顯鑫環(huán)節(jié)肚子疼的情況,讓他可以更舒服,疼痛感從9分下降到2分...
    Betty麗麗閱讀 144評(píng)論 0 0
  • 追劇到凌晨四點(diǎn)才睡覺(jué),幾乎算通宵,這對(duì)于我多年不追劇,不看電視的人來(lái)說(shuō),真的是罕見(jiàn)。之所以要看《楚喬傳》,也是因?yàn)?..
    正好閑聊閱讀 437評(píng)論 0 3