在我目前的设置中,我有一个
typedef std::function<void (MyClass&, std::vector<std::string>) MyFunction;
std::map<std::string, MyFunction> dispatch_map;
我用宏在其中注册了我的函数。但是,我有一个问题:参数作为字符串向量传递,我必须在函数内部进行转换。我宁愿在调度程序级别的函数之外进行这种转换。这可能吗?函数签名在编译时是已知的,并且在运行时永远不会改变。
如果您可以使用 boost,那么这是我认为您正在尝试做的一个示例(尽管也可以使用std
,但我个人坚持使用 boost):
typedef boost::function<void ( MyClass&, const std::vector<std::string>& ) MyFunction;
std::map<std::string, MyFunction> dispatch_map;
namespace phx = boost::phoenix;
namespace an = boost::phoenix::arg_names;
dispatch_map.insert( std::make_pair( "someKey", phx::bind( &MyClass::CallBack, an::_1, phx::bind( &boost::lexical_cast< int, std::string >, phx::at( an::_2, 0 ) ) ) ) );
dispatch_map["someKey"]( someClass, std::vector< std::string >() );
然而,由于这种嵌套很快变得相当不可读,通常最好创建一个帮助器(自由函数,或者更好的是一个惰性函数)来进行转换。
您可以使用可变参数模板和一些模板/虚拟技术走得很远。使用以下代码,您将能够执行以下操作:
std::string select_string (bool cond, std::string a, std::string b) {
return cond ? a : b;
}
int main () {
Registry reg;
reg.set ("select_it", select_string);
reg.invoke ("select_it", "1 John Wayne"));
reg.invoke ("select_it", "0 John Wayne"));
}
输出:
John
Wayne
这些代码是示例性的。您应该优化它以在参数列表扩展中提供完美的转发更少的冗余。
标头和测试功能
#include <functional>
#include <string>
#include <sstream>
#include <istream>
#include <iostream>
#include <tuple>
std::string select_string (bool cond, std::string a, std::string b) {
return cond ? a : b;
}
这有助于我们解析字符串并将结果放入元组中:
//----------------------------------------------------------------------------------
template <typename Tuple, int Curr, int Max> struct init_args_helper;
template <typename Tuple, int Max>
struct init_args_helper<Tuple, Max, Max> {
void operator() (Tuple &, std::istream &) {}
};
template <typename Tuple, int Curr, int Max>
struct init_args_helper {
void operator() (Tuple &tup, std::istream &is) {
is >> std::get<Curr>(tup);
return init_args_helper<Tuple, Curr+1, Max>() (tup, is);
}
};
template <int Max, typename Tuple>
void init_args (Tuple &tup, std::istream &ss)
{
init_args_helper<Tuple, 0, Max>() (tup, ss);
}
这将函数指针和元组展开为函数调用(通过函数指针):
//----------------------------------------------------------------------------------
template <int ParamIndex, int Max, typename Ret, typename ...Args>
struct unfold_helper;
template <int Max, typename Ret, typename ...Args>
struct unfold_helper<Max, Max, Ret, Args...> {
template <typename Tuple, typename ...Params>
Ret unfold (Ret (*fun) (Args...), Tuple tup, Params ...params)
{
return fun (params...);
}
};
template <int ParamIndex, int Max, typename Ret, typename ...Args>
struct unfold_helper {
template <typename Tuple, typename ...Params>
Ret unfold (Ret (*fun) (Args...), Tuple tup, Params ...params)
{
return unfold_helper<ParamIndex+1, Max, Ret, Args...> ().
unfold(fun, tup, params..., std::get<ParamIndex>(tup));
}
};
template <typename Ret, typename ...Args>
Ret unfold (Ret (*fun) (Args...), std::tuple<Args...> tup) {
return unfold_helper<0, sizeof...(Args), Ret, Args...> ().unfold(fun, tup);
}
这个函数把它放在一起:
//----------------------------------------------------------------------------------
template <typename Ret, typename ...Args>
Ret foo (Ret (*fun) (Args...), std::string mayhem) {
// Use a stringstream for trivial parsing.
std::istringstream ss;
ss.str (mayhem);
// Use a tuple to store our parameters somewhere.
// We could later get some more performance by combining the parsing
// and the calling.
std::tuple<Args...> params;
init_args<sizeof...(Args)> (params, ss);
// This demondstrates expanding the tuple to full parameter lists.
return unfold<Ret> (fun, params);
}
这是我们的测试:
int main () {
std::cout << foo (select_string, "0 John Wayne") << '\n';
std::cout << foo (select_string, "1 John Wayne") << '\n';
}
std::function<>
而不是裸函数指针根据上面的代码,编写一个函数注册表很简单:
class FunMeta {
public:
virtual ~FunMeta () {}
virtual boost::any call (std::string args) const = 0;
};
template <typename Ret, typename ...Args>
class ConcreteFunMeta : public FunMeta {
public:
ConcreteFunMeta (Ret (*fun) (Args...)) : fun(fun) {}
boost::any call (std::string args) const {
// Use a stringstream for trivial parsing.
std::istringstream ss;
ss.str (args);
// Use a tuple to store our parameters somewhere.
// We could later get some more performance by combining the parsing
// and the calling.
std::tuple<Args...> params;
init_args<sizeof...(Args)> (params, ss);
// This demondstrates expanding the tuple to full parameter lists.
return unfold<Ret> (fun, params);
}
private:
Ret (*fun) (Args...);
};
class Registry {
public:
template <typename Ret, typename ...Args>
void set (std::string name, Ret (*fun) (Args...)) {
funs[name].reset (new ConcreteFunMeta<Ret, Args...> (fun));
}
boost::any invoke (std::string name, std::string args) const {
const auto it = funs.find (name);
if (it == funs.end())
throw std::runtime_error ("meh");
return it->second->call (args);
}
private:
// You could use a multimap to support function overloading.
std::map<std::string, std::shared_ptr<FunMeta>> funs;
};
甚至可以考虑支持函数重载,使用多映射并根据传递的参数上的内容进行调度决策。
以下是如何使用它:
int main () {
Registry reg;
reg.set ("select_it", select_string);
std::cout << boost::any_cast<std::string> (reg.invoke ("select_it", "0 John Wayne")) << '\n'
<< boost::any_cast<std::string> (reg.invoke ("select_it", "1 John Wayne")) << '\n';
}
有趣的问题。这在 C++ 中并不是微不足道的,我在 C++11 中编写了一个独立的实现。在 C++03 中也可以这样做,但代码的可读性(甚至)会更差。
#include <iostream>
#include <sstream>
#include <string>
#include <functional>
#include <vector>
#include <cassert>
#include <map>
using namespace std;
// string to target type conversion. Can replace with boost::lexical_cast.
template<class T> T fromString(const string& str)
{ stringstream s(str); T r; s >> r; return r; }
// recursive construction of function call with converted arguments
template<class... Types> struct Rec;
template<> struct Rec<> { // no parameters
template<class F> static void call
(const F& f, const vector<string>&, int) { f(); }
};
template<class Type> struct Rec< Type > { // one parameter
template<class F> static void call
(const F& f, const vector<string>& arg, int index) {
f(fromString<Type>(arg[index]));
}
};
template<class FirstType, class... NextTypes>
struct Rec< FirstType, NextTypes... > { // many parameters
template<class F> static void call
(const F& f, const vector<string>& arg, int index) {
Rec<NextTypes...>::call(
bind1st(f, fromString<FirstType>(arg[index])), // convert 1st param
arg,
index + 1
);
}
};
template<class... Types> void call // std::function call with strings
(const function<void(Types...)>& f, const vector<string>& args) {
assert(args.size() == sizeof...(Types));
Rec<Types...>::call(f, args, 0);
}
template<class... Types> void call // c function call with strings
(void (*f)(Types...), const vector<string>& args) {
call(function<void(Types...)>(f), args);
}
// transformas arbitrary function to take strings parameters
template<class F> function<void(const vector<string>&)> wrap(const F& f) {
return [&] (const vector<string>& args) -> void { call(f, args); };
}
// the dynamic dispatch table and registration routines
map<string, function<void(const vector<string>&)> > table;
template<class F> void registerFunc(const string& name, const F& f) {
table.insert(make_pair(name, wrap(f)));
}
#define smartRegister(F) registerFunc(#F, F)
// some dummy functions
void f(int x, float y) { cout << "f: " << x << ", " << y << endl; }
void g(float x) { cout << "g: " << x << endl; }
// demo to show it all works;)
int main() {
smartRegister(f);
smartRegister(g);
table["f"]({"1", "2.0"});
return 0;
}
此外,对于性能,最好使用 unordered_map 而不是 map,如果您只有常规 C 函数,可能会避免 std::function 开销。当然,这只有在调度时间与函数运行时间相比很重要的情况下才有意义。
如果我对您的理解正确,您想注册void MyClass::Foo(int)
,并且void MyClass::Bar(float)
接受将有一个演员 from std::string
toint
或float
视情况而定。
为此,您需要一个辅助类:
class Argument {
std::string s;
Argument(std::string const& s) : s(s) { }
template<typename T> operator T { return boost::lexical_cast<T>(s); }
};
这使得可以将void MyClass::Foo(int)
和都包装void MyClass::Bar(float)
在一个std::function<void(MyClass, Argument))>
.
不,C++ 没有为这种情况提供任何便利。