我试图在地图中存储一组std :: function(在GCC 4.5下)
我想得到两种东西:
我想我用类Command和Manager实现了第一个:
class Command
{
std::function<void()> f_;
public:
Command() {}
Command(std::function<void()> f) : f_(f) {}
void execute() { if(f_) f_(); }
};
class CommandManager
{
typedef map<string, Command*> FMap;
public :
void add(string name, Command* cmd)
{
fmap1.insert(pair<string, Command*>(name, cmd));
}
void execute(string name)
{
FMap::const_iterator it = fmap1.find(name);
if(it != fmap1.end())
{
Command* c = it->second;
c->execute();
}
}
private :
FMap fmap1;
};
Run Code Online (Sandbox Code Playgroud)
可以像这样使用:
class Print{
public:
void print1(string s, string s1){ cout<<"print1 : "<<"s : "<<s<<" s1 : "<<s1<<endl; }
int print2(){ cout<<"print2"<<endl; return 2;}
};
#include <string>
#include <functional>
int main()
{
Print p = Print();
function<void()> f1(bind(&Print::print1, &p, string("test1"), string("test2")));
function<int()> f2(bind(&Print::print2, &p));
CommandManager cmdMgr = CommandManager();
cmdMgr.add("print1", new Command(f1));
cmdMgr.execute("print1");
cmdMgr.add("print2", new Command(f2));
cmdMgr.execute("print2");
return 0;
}
Run Code Online (Sandbox Code Playgroud)
现在我希望能够做到这一点:
int main()
{
Print p = Print();
function<void(string, string)> f1(bind(&Print::print1, &p, placeholders::_1, placeholders::_2));
CommandManager cmdMgr = CommandManager();
cmdMgr.add("print1", new Command(f1));
cmdMgr.execute("print1", string("test1"), string("test2"));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
有没有办法,例如使用类型擦除?
您可以使用动态强制转换来确定运行时列表中函数的类型.请注意,我添加了shared_ptr以消除原始样本中的内存泄漏.如果使用错误的参数调用execute方法(如果dynamic_cast产生0),则可能要抛出异常.
用法:
void x() {}
void y(int ) {}
void main() {
CommandManager m;
m.add("print", Command<>(x));
m.add("print1", Command<int>(y));
m.execute("print");
m.execute("print1", 1);
}
Run Code Online (Sandbox Code Playgroud)
代码(具有可变参数模板支持,例如gcc-4.5):
#include <functional>
#include <map>
#include <string>
#include <memory>
using namespace std;
class BaseCommand
{
public:
virtual ~BaseCommand() {}
};
template <class... ArgTypes>
class Command : public BaseCommand
{
typedef std::function<void(ArgTypes...)> FuncType;
FuncType f_;
public:
Command() {}
Command(FuncType f) : f_(f) {}
void operator()(ArgTypes... args) { if(f_) f_(args...); }
};
class CommandManager
{
typedef shared_ptr<BaseCommand> BaseCommandPtr;
typedef map<string, BaseCommandPtr> FMap;
public :
template <class T>
void add(string name, const T& cmd)
{
fmap1.insert(pair<string, BaseCommandPtr>(name, BaseCommandPtr(new T(cmd))));
}
template <class... ArgTypes>
void execute(string name, ArgTypes... args)
{
typedef Command<ArgTypes...> CommandType;
FMap::const_iterator it = fmap1.find(name);
if(it != fmap1.end())
{
CommandType* c = dynamic_cast<CommandType*>(it->second.get());
if(c)
{
(*c)(args...);
}
}
}
private :
FMap fmap1;
};
Run Code Online (Sandbox Code Playgroud)
没有可变参数模板支持(例如VS2010):
#include <functional>
#include <map>
#include <string>
#include <memory>
using namespace std;
class Ignored;
class BaseCommand
{
public:
virtual ~BaseCommand() = 0 {};
};
template <class A1 = Ignored>
class Command : public BaseCommand
{
typedef std::function<void(A1)> FuncType;
FuncType f_;
public:
Command() {}
Command(FuncType f) : f_(f) {}
void operator()(const A1& a1) { if(f_) f_(a1); }
};
template <>
class Command<Ignored> : public BaseCommand
{
typedef std::function<void()> FuncType;
FuncType f_;
public:
Command() {}
Command(FuncType f) : f_(f) {}
void operator()() { if(f_) f_(); }
};
class CommandManager
{
typedef shared_ptr<BaseCommand> BaseCommandPtr;
typedef map<string, BaseCommandPtr> FMap;
public :
template <class T>
void add(string name, const T& cmd)
{
fmap1.insert(pair<string, BaseCommandPtr>(name, BaseCommandPtr(new T(cmd))));
}
template <class A1>
void execute(string name, const A1& a1)
{
typedef Command<A1> CommandType;
FMap::const_iterator it = fmap1.find(name);
if(it != fmap1.end())
{
CommandType* c = dynamic_cast<CommandType*>(it->second.get());
if(c)
{
(*c)(a1);
}
}
}
void execute(string name)
{
typedef Command<> CommandType;
FMap::const_iterator it = fmap1.find(name);
if(it != fmap1.end())
{
CommandType* c = dynamic_cast<CommandType*>(it->second.get());
if(c)
{
(*c)();
}
}
}
private :
FMap fmap1;
};
Run Code Online (Sandbox Code Playgroud)
如果没有一些认真的运行时工作和相关成本,您想要做的事情是不可能的。最简单的解决方案当然是在地图中存储boost::any(any_function从未将其放入 boost 中)并进行必要的转换(或添加一些运行时数据来告诉您要进行哪个转换),尽管您应该避免这样做任何成本并采用固定参数或无参数。然后,您的用户可以使用修改其函数bind来匹配您所需的签名。
编辑:在您当前的方案中,我认为没有理由CommandManager存储Command*在地图中。
Edit2:您还可以删除返回类型。这对于您的用例来说可能没问题,但使其不那么通用。
Edit3:我使用any. 我觉得有一些缺陷,我真的不知道这应该实现什么,但它是这样的:
#include <iostream>
#include <string>
#include <map>
#include <functional>
#include <boost/any.hpp>
class AnyCaller
{
std::map<std::string, boost::any> calls;
public:
AnyCaller() {}
void add(const std::string& name, const boost::any& fun) {
calls[name] = fun;
}
// we always use the throwing version of any_cast
// icbb by error checking
// no arg version
template<typename Ret>
Ret call(const std::string& s) {
const boost::any& a = calls[s];
return boost::any_cast< std::function<Ret(void)> >(a)();
}
// this should be a variadic template to be actually usable
template<typename Ret, typename T>
Ret call(const std::string& s, T&& arg) {
// we have to assume that our users know what we are actually returning here
const boost::any& a = calls[s];
return boost::any_cast< std::function<Ret(T)> >(a)(std::forward<T>(arg));
}
virtual ~AnyCaller() {}
};
int foo() { std::cout << "foo" << std::endl; return 1; }
double foo2(int i) { std::cout << "foo2" << std::endl; return double(i); }
int main()
{
AnyCaller c;
c.add("foo", std::function<int(void)>(foo));
c.add("foo2", std::function<double(int)>(foo2));
c.call<int>("foo");
c.call<double, int>("foo2", 1);
// this should throw
c.call<double, int>("foo", 1);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
至于使用固定签名的示例。想想你要存储的函数的最自然的表示是什么(看看你的Command例子,我假设它是std::function<void(void)>。存储这种类型的函数,每当你的用户尝试使用它时,他必须使用bind任何函数他想用,所以就匹配这个签名。
您的Command类构造函数需要一个function<void()>. 你正试图给它喂食function<void(string,string)>。这不会进行类型检查。
如果您需要接受可变参数的函数(例如printf),您将需要function<>和execute()接受可变参数。您需要知道如何使用它(特别是,您需要一个固定的第一个参数)。然后,您负责类型安全,就像printf.
如果您只需要可变数量的字符串参数,请使用接受字符串向量等的函数。
这一切都与 没有任何关系std::map。无论您可以在普通旧变量中存储什么,您都可以存储在其中std::map。