需求背景
有时候静态编译太不灵活,我们需要更灵活的运行时操作。
又或真假设你在开发一个脚本,想注册本地的C++函数到脚本语言的标准库中。例如gdscript的Callable。
下面是一个我的一个简单的实现。我们假设脚本语言中的变量类型是std::any。根据情况不同实现细节也可能不同。
实现
首先我们需要对可调用对象进行一个抽象的表示。
struct Callable {
int _argc = 0;
virtual std::any call(const std::vector<std::any>& args) = 0;
virtual ~Callable() = default;
};
很简单,定义一个抽象方法call,参数列表使用vector<any>来表示,。然后用argc表示这个可调用的对象可接收多少个参数。
类方法
这里仅仅实现一个比较复杂的可调用对象,类方法。这是我们准备的测试类。
struct Foo {
int a = 10;
int foo(int n, char ch, const std::string* msg)
{
a += n;
cout << ch << ": " << *msg << a << endl;
return a;
}
int bar(int n)
{
cout << "--" << n << "--" << endl;
return n;
}
};
应当注意的是,类方法并不能简单的转换为函数指针。
比如上述的bar的类型不是int(*)(int)
,而是int(Foo::*)(int)
。应当注意这点。随后我们继承这个Callable,一个名为ClassMethod的类来包装类方法。这里我们实现了使用了和std::functional差不多的形式。为了表达直观,我们用类似ClassMethod<ClassName, int(int)>
这样的形式,而不是ClassMethod<int,int>
这样的形式。
template<typename... Args>
struct ClassMethod; //永远也不会匹配到这个模板,所以不用定义。
template<typename ClassName, typename Ret, typename ... Args>
struct ClassMethod<ClassName, Ret(Args...)> : public Callable {/*TODO*/}
//使用方式
<ClassMethod<Foo, int(int, char, const std::string*)>> callable;
Ret表示返回值,Args表示一个参数包,也就是参数的类型列表。后续我们使用包展开来对其进行操作。
ClassMethod内部:
static con