一、函數反射
在實際的編程中,類和結構體應用最多,但也最難。這里先分析函數反射,類和結構體放到后面在分析。函數是什么?其實在PC看來就是一個地址,在編譯順看來就是一個符號(廢話啊)。函數反射的應用也非常多,比如通過一個字符串來得到相關的API調用。這個在一些動態調用中,非常有用。
舉一個簡單例子,一般C/C++開發者都使用過函數指針,而函數指針就可以實現一些和函數反射有點類似的功能。一般函數指針在應用時,都是通過值來判斷是什么來決定調用哪個函數指針,這些值其實就可以是字符串類型,這就和反射很像了。但函數指針的實現有點小問題在于,一個函數指針,其特征(名稱、參數個數、參數類型)基本就定了下來。這就不如反射靈活了。
有的開發者可能說,可以使用變參、變參模板啊。非常棒。
二、實現方式
先實現一個初級版本,通過std:function來實現一個映射版本:
#include <iostream>
#include <unordered_map>
#include <functional>
#include <string>void getData(int t)
{std::cout << "call getData function,pars is:"<<t << std::endl;
}
void getContent(int t)
{std::cout << "call getContent function,pars is:"<<t << std::endl;
}std::unordered_map<std::string, std::function<void(int)>> umap;
void initMap()
{umap.emplace("getData",getData);umap.emplace("getContent",getContent);
}int main()
{initMap();int d = 100;if (umap.count("getData")){auto func = umap["getData"];func(d);}std::cout << "end" << std::endl;
}
代碼很簡單,但也很容易看明白。可前面提到過了,這種方法局限性還是有的,無法實現不同參數和參數類型的函數。這里有一種取巧的方法,可以用一個包含std::any的容器std::vector來組織一下,但這個就有一個問題,處理起來還是不方便。網上還有使用json字符串的,這個說法更麻煩了。如果本身反射就帶著json處理還好,否則寫個簡單應用還需要帶個json庫,可就麻煩了。
三、利用模板萬能函數
在前面分析過萬能函數,可以在這個基礎上實現一個動態處理函數反射的類:
#pragma once
#include <string>
#include <unordered_map>template <class T, class R, typename... Args>
class CppDelegate
{R(T::* func_)(Args...);//萬能函數typedef decltype(func_) FUNC;//using FuncGloabl = R *(*)(Args...);public:CppDelegate() {}void AddFunction(T *t,const std::string & funcname, FUNC func ){umap_.emplace(funcname,func);umap1_.emplace(funcname,t);}template<typename ...Args>R StartFunc(const std::string& funcname,Args...args){auto type = this->getClass(funcname);auto func = this->getFunc(funcname);if (type != nullptr && func != nullptr){return (type->*func)(std::forward<Args>(args) ...);}return R();}
private:FUNC getFunc(const std::string &funcname){if (umap_.count(funcname) > 0){return umap_[funcname];}return nullptr;}T* getClass(const std::string& name){if (umap1_.count(name) > 0){return umap1_[name];}return nullptr;}private:std::unordered_map<std::string, FUNC> umap_;std::unordered_map<std::string, T*> umap1_;
};
class Data
{
public:Data() {}~Data() = default;
public:int GetData(int a) { std::cout << "call getData function,a value:"<< a<< std::endl; return 0; };int GetContent(int a, int b) { std::cout << "call getContent function:" << std::endl; return 0; };
};
Data* d = new Data;
void testReflect()
{CppDelegate<Data, int,int> cpp;cpp.AddFunction(d,"getData",&Data::GetData);auto f = cpp.StartFunc("getData",100);std::cout << "f is:" << std::endl;
}
void testVoid() {return void();
}
int main()
{testVoid();//這個在VS中沒有問題testReflect();return 0;
}
其實如果只是適配靜態和全局函數,這個就非常簡單了,這里需要適配類成員函數,所以比較麻煩。上面的代碼還有幾個問題:
1、不同類的不同函數如何存儲在一個容器中
2、return R()如何處理void 等特殊情況
3、如何保證t*的生命周期
解決其來也有辦法,只是怎么看更優雅一些。第一個可以在調用類上再抽象一層;第二個可以用概念或者SFINAE控制;第三個就比較麻煩了,不過,目前這樣做也可以保證基本使用。
四、總結
不斷的抽象實現可以保證設計上的依賴于抽象而不依賴于實現,也就使得代碼更有普適性。但多層次的抽象導致的結果可能是代碼閱讀上的困難和維護上不方便。這個就是仁者見仁了,一般來說,對于庫等升級比較正式而且不怎么頻繁的項目可以盡量抽象,而對于應用層,抽象要適當。
不過在現在的環境下,就根據情況自己選擇吧。