C++中如何自己封装一个Callable

需求背景

有时候静态编译太不灵活,我们需要更灵活的运行时操作。

又或真假设你在开发一个脚本,想注册本地的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;
	}

}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值