赞
踩
有时候静态编译太不灵活,我们需要更灵活的运行时操作。
又或真假设你在开发一个脚本,想注册本地的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 constexpr int Argc = sizeof...(Args);
using Callee = Ret(ClassName::*)(Args...);
Argc可在编译阶段求解,Callee是一个别名,定义方式就是上述所说的类方法的类型。不过是换成了模板参数。
类方法在调用的时候实际上隐藏了一个参数,就是this指针。所以我们需要一个指向对象的指针来表示this,和一个Callee来表示调用的具体是哪个方法。因为不同的方法可能有相同的参数名称。
ClassMethod内部:
ClassName* _class = nullptr;
Callee _callee = nullptr;
ClassName* set_class(ClassName* ptr)
{
ClassName* old = _class;
_class = ptr;
return old;
}
Callee* set_callee(Callee callee)
{
Callee* old = _callee;
_callee = callee;
return old;
}
我们需要对Args,也就是参数包,进行转发调用。
Ret call_impl(Args&& ... args)
{
return (_class->*_callee)(std::forward<Args>(args)...);
}
你可能会惊讶_class->*_callee
这样的调用形式,但实际上这就是一个固定的写法,没什么可惊讶的,
剩下的最后一部就是将any进行类型转换并检查类型。这一步我们重写call来完成:
any call(const std::vector<std::any>& args) override
{
//检查参数个数是否匹配
if (args.size()!=Argc) {
std::string msg = std::format("args size not match expect {} but is {}", Argc, args.size());
throw std::logic_error(msg.c_str());
}
//应当如何包展开这个Args
auto ret = call_impl(??? Args...);
return ret;
}
别忘了any_cast,这可以进行类型检查。但我们同时还需要一个计数器,来看表示我们检查对象所处参数中的索引位置。这样才能访问到对应位置的参数。使用一个类包装这个操作。
struct CallHelper { int index; explicit CallHelper(int argc) :index(argc-1) { } template<typename T> T operator()(const std::vector<any>& list) { try { //捕获异常,添加Index的提示信息 auto ret = std::any_cast<T>(list[index]); index--; return ret; } catch (const std::bad_any_cast& err) { // 再抛出异常,或者干点他什么的都可以 auto msg = std::format("{}. Index: {}.", err.what(), index); throw std::logic_error(msg.c_str()); } } };
注意每次调用index不是从0开始递增,而是从argc-1开始向0递减。这是因为包展开后是如下的形式:
// Args && ... args
std::forward<Args...>(args);
//假设Args为int,double,char
//会被展开为这样的形式
std::forward<int>(v1),std::forward<int>(v2),std::forward<int>(v3)
展开成一系列的由逗号分割的表达式。重要的是——他是从右向左求值的。
any call(const std::vector<std::any>& args) override
{
if (args.size()!=Argc) {
std::string msg = std::format("args size not match expect {} but is {}", Argc, args.size());
throw std::logic_error(msg.c_str());
}
//*******
CallHelper helper(Argc);
auto ret = call_impl(helper.operator()<Args>(args)...);
//********
return ret;
}
Args会按照helper.operator()
这个模式进行展开,从右向左的求值,使用index进行any的索引。区别于Args && .. arg
中的args,这处的args只是一个vector<any>,和call_impl的args不是一个东西。也就是说,它会被展开成为这样:
//假设Args是int,char,double
helper.operator()<Args>(args)
//会被展开为
helper.operator()<int>(args),helper.operator()<char>(args),helper.operator()<double>(args)
测试:
int main() { Foo foo; std::unique_ptr<Callable> test_foo = std::make_unique<ClassMethod<Foo, int(int, char, const std::string*)>>(&foo, &Foo::foo); std::unique_ptr<Callable> test_bar = std::make_unique<ClassMethod<Foo,int(int)>>(&foo,&Foo::bar); test_bar->call({10}); string msg = "hello world"; test_foo->call({make_any<int>(10), make_any<char>('A'), make_any<const std::string*>(&msg)}); try { test_foo->call({}); } catch (const std::exception & e) { cerr << e.what() << endl; } try { test_foo->call({1,2,3}); } catch (const std::exception & e) { cerr << e.what() << endl; } }
输出:
--10--
A: hello world20
args size not match expect 3 but is 0
bad any_cast. Index: 2.
进程已结束,退出代码为 0
至于其他的可调用类型例如函数指针,函数对象,则都可以使用std::functional来当作中间层来实现,就不展开说了。
#include <iostream> #include <vector> #include <memory> #include <any> #include <format> using namespace std; 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; } }; struct Callable { int _argc = 0; virtual std::any call(const std::vector<std::any>& args) = 0; virtual ~Callable() = default; }; struct CallHelper { int index; explicit CallHelper(int argc) :index(argc-1) { } template<typename T> T operator()(const std::vector<any>& list) { try { auto ret = std::any_cast<T>(list[index]); index--; return ret; } catch (const std::bad_any_cast& err) { auto msg = std::format("{}. Index: {}.", err.what(), index); throw std::logic_error(msg.c_str()); } } }; template<typename... Args> struct ClassMethod; template<typename ClassName, typename Ret, typename ... Args> struct ClassMethod<ClassName, Ret(Args...)> : public Callable { static constexpr int Argc = sizeof...(Args); using Callee = Ret(ClassName::*)(Args...); explicit ClassMethod(ClassName* _class_name, Callee callee) { _class = _class_name; _callee = callee; _argc = sizeof...(Args); } ClassName* _class = nullptr; Callee _callee = nullptr; ClassName* set_class(ClassName* ptr) { ClassName* old = _class; _class = ptr; return old; } Callee* set_callee(Callee callee) { Callee* old = _callee; _callee = callee; return old; } Ret call_impl(Args&& ... args) { return (_class->*_callee)(std::forward<Args>(args)...); } any call(const std::vector<std::any>& args) override { if (args.size()!=Argc) { std::string msg = std::format("args size not match expect {} but is {}", Argc, args.size()); throw std::logic_error(msg.c_str()); } CallHelper helper(Argc); auto ret = call_impl(helper.operator()<Args>(args)...); return ret; } }; int main() { Foo foo; std::unique_ptr<Callable> test_foo = std::make_unique<ClassMethod<Foo, int(int, char, const std::string*)>>(&foo, &Foo::foo); std::unique_ptr<Callable> test_bar = std::make_unique<ClassMethod<Foo,int(int)>>(&foo,&Foo::bar); test_bar->call({10}); string msg = "hello world"; test_foo->call({make_any<int>(10), make_any<char>('A'), make_any<const std::string*>(&msg)}); try { test_foo->call({}); } catch (const std::exception & e) { cerr << e.what() << endl; } try { test_foo->call({1,2,3}); } catch (const std::exception & e) { cerr << e.what() << endl; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。