一、函数反射
在实际的编程中,类和结构体应用最多,但也最难。这里先分析函数反射,类和结构体放到后面在分析。函数是什么?其实在PC看来就是一个地址,在编译顺看来就是一个符号(废话啊)。函数反射的应用也非常多,比如通过一个字符串来得到相关的API调用。这个在一些动态调用中,非常有用。
举一个简单例子,一般C/C++开发者都使用过函数指针,而函数指针就可以实现一些和函数反射有点类似的功能。一般函数指针在应用时,都是通过值来判断是什么来决定调用哪个函数指针,这些值其实就可以是字符串类型,这就和反射很像了。但函数指针的实现有点小问题在于,一个函数指针,其特征(名称、参数个数、参数类型)基本就定了下来。这就不如反射灵活了。
有的开发者可能说,可以使用变参、变参模板啊。非常棒。
二、实现方式
先实现一个初级版本,通过std:function来实现一个映射版本:
c
#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库,可就麻烦了。
三、利用模板万能函数
在前面分析过万能函数,可以在这个基础上实现一个动态处理函数反射的类:
c
#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控制;第三个就比较麻烦了,不过,目前这样做也可以保证基本使用。
四、总结
不断的抽象实现可以保证设计上的依赖于抽象而不依赖于实现,也就使得代码更有普适性。但多层次的抽象导致的结果可能是代码阅读上的困难和维护上不方便。这个就是仁者见仁了,一般来说,对于库等升级比较正式而且不怎么频繁的项目可以尽量抽象,而对于应用层,抽象要适当。
不过在现在的环境下,就根据情况自己选择吧。