C ++ 0x:在std :: map中存储任何类型的std :: function

时间:2011-10-02 01:16:31

标签: c++ bind c++11

我正在尝试在地图中存储一组std :: function(在GCC 4.5下)

我想得到两种东西:

  • 存储已经传递参数的函数;那你就是 拨打f()
  • 存储没有参数的函数;然后你必须打电话 F(...)

我认为我使用类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;

};

可以像这样使用:

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

现在我希望能够做到这一点:

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

有没有办法,例如使用类型擦除?

3 个答案:

答案 0 :(得分:8)

您可以使用动态强制转换来确定运行时列表中函数的类型。 请注意,我添加了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);
}

代码(例如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;
};

没有可变参数模板支持(例如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;
};

答案 1 :(得分:3)

如果没有一些严肃的运行时工作和相关的成本,你不可能做的事情。最简单的解决方案当然只是在地图中存储boost::anyany_function从未使其成为提升)并进行必要的转换(或添加一些运行时数据,告诉您要进行哪个转换),虽然你应该不惜一切代价避免这种情况,并使用固定的参数或没有参数。 然后,您的用户可以使用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;
}

至于使用固定签名的示例。只要想想你要存储的函数最自然的表示方式(查看Command示例,我认为它是std::function<void(void)>。存储此类型的函数以及每当您的用户使用时尝试使用它,他必须bind他想要使用的任何功能,所以它匹配这个签名。

答案 2 :(得分:2)

您的Command类构造函数需要function<void()>。您正尝试将其function<void(string,string)>提供给它。这不会成为一个问题。

如果需要接受变量参数的函数(如printf),则需要接受变量参数的function<>execute()。您需要知道如何使用它(特别是,您需要一个固定的第一个参数)。然后,您负责类型安全,就像printf一样。

如果您只需要可变数量的字符串参数,请使用接受例如字符串向量。

所有这些与std::map无关。无论您存储在普通旧变量中,您都可以存储在std::map中。