当前位置:   article > 正文

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

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;
};	
  • 1
  • 2
  • 3
  • 4
  • 5

很简单,定义一个抽象方法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;
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

应当注意的是,类方法并不能简单的转换为函数指针。

比如上述的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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Ret表示返回值,Args表示一个参数包,也就是参数的类型列表。后续我们使用包展开来对其进行操作。

ClassMethod内部:

  static constexpr int Argc = sizeof...(Args);
  using Callee = Ret(ClassName::*)(Args...);
  • 1
  • 2

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;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

我们需要对Args,也就是参数包,进行转发调用。

 Ret call_impl(Args&& ... args)
  {
	  return (_class->*_callee)(std::forward<Args>(args)...);
  }
  • 1
  • 2
  • 3
  • 4

你可能会惊讶_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;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

别忘了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());
	  }
  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

注意每次调用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)
  • 1
  • 2
  • 3
  • 4
  • 5

展开成一系列的由逗号分割的表达式。重要的是——他是从右向左求值的。

  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;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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)
  • 1
  • 2
  • 3
  • 4

测试:

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;
	}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

输出:

--10--
A: hello world20
args size not match expect 3 but is 0
bad any_cast. Index: 2.

进程已结束,退出代码为 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

至于其他的可调用类型例如函数指针,函数对象,则都可以使用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;
	}

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/555629
推荐阅读
相关标签
  

闽ICP备14008679号