C ++ 0x: хранение любого типа std :: function в std :: map - PullRequest
8 голосов
/ 02 октября 2011

Я пытаюсь сохранить набор std :: function на карте (в соответствии с GCC 4.5)

Я бы хотел получить 2 вида вещей:

  • сохранение функций с уже переданными аргументами; тогда у вас просто есть позвонить 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 ]

8 голосов
/ 02 октября 2011

Вы можете использовать динамическое приведение, чтобы определить тип функции в списке во время выполнения. Обратите внимание, что я добавил 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;
};
3 голосов
/ 02 октября 2011

То, что вы пытаетесь сделать, невозможно без серьезной работы во время выполнения и сопутствующих затрат.Самым простым решением, конечно же, было бы просто сохранить 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;
}

Что касается примера использования фиксированной подписи.Подумайте, что было бы наиболее естественным представлением функции, которую вы собираетесь хранить (глядя на ваш Command пример, я бы предположил, что это std::function<void(void)>. Храните функции этого типа и всякий раз, когда один из ваших пользователей пытается их использоватьон должен bind любую функцию, которую он хочет использовать, поэтому она соответствует этой подписи.

2 голосов
/ 02 октября 2011

Ваш конструктор класса Command нуждается в function<void()>. Вы пытаетесь накормить его function<void(string,string)>. Это не будет проверка типов.

Если вам нужны функции, которые принимают переменные аргументы (например, printf), вам понадобятся function<> и execute(), которые принимают переменные аргументы. Вам нужно знать, как с этим работать (в частности, вам нужен фиксированный первый аргумент). Затем вы отвечаете за безопасность типов, как и в случае с printf.

Если вам просто нужно переменное количество строковых аргументов, используйте функции, которые принимают, например, векторы строк.

Все это не имеет ничего общего с std::map. Все, что вы можете хранить в простой старой переменной, вы можете хранить и в std::map.

...