【问题标题】:Using boost::bind with boost::function: retrieve binded variable type将 boost::bind 与 boost::function 一起使用:检索绑定的变量类型
【发布时间】:2011-12-15 04:17:05
【问题描述】:

有什么方法可以检索有关 boost::bind 限制的参数的信息,还是需要手动存储?

即:

在.h中

class MyClass
{
    void foo(int a);
    void foo2(double b);
    void bar();
    void execute();
    int _myint;
    double _mydouble;
}

在.cpp中

MyClass::bar()
{
    vector<boost::function<void(void)> myVector;
    myVector.push_back(boost::bind(&MyClass::foo, this, MyClass::_myint);
    myVector.push_back(boost::bind(&MyClass::foo2, this, MyClass::_mydouble);
}
MyClass::execute(char* param)
{

    boost::function<void(void)> f  = myVector[0];
    //MAGIC goes here
    //somehow know that an int parameter was bound
    _myint = atoi(param);
    //--------------------------------------
    f();
}

【问题讨论】:

  • 这是一个有趣的理论问题,但我认为您的示例没有充分说明为什么您需要这样做。
  • 为什么是我正在为我的应用程序实现命令行用户输入。我希望能够调用各种类的预先存在的函数:/exec &lt;functionName&gt; &lt;param1&gt; &lt;param2&gt;。该向量实际上是一个带有 &lt;functionName&gt; 调用的关联字符串的映射...如果您对如何实现这一点有更好的了解,请告诉 :D
  • @Smash:你在发明脚本引擎吗?
  • @sehe 如果你这么想,那我可能是不由自主的:)

标签: c++ boost types


【解决方案1】:

boost::function 有一些你应该可以使用的公共类型定义。

template<typename Signature>  // Function type R (T1, T2, ..., TN)
class function : public functionN<R, T1, T2, ..., TN> {
public:
  // types
  typedef R  result_type;         
  typedef T1 argument_type;         // If N == 1
  typedef T1 first_argument_type;   // If N == 2
  typedef T2 second_argument_type;  // If N == 2
  typedef T1 arg1_type;           
  typedef T2 arg2_type;  

  // ...

  // static constants
  static const int arity = N;

稍微改编自boost::function reference。

【讨论】:

  • 这不行,因为boost::function没有参数。
【解决方案2】:

由于您似乎只是在寻找触发函数以响应已解析文本的方法,因此我提出了这个基于 Boost Spirit 解析器的示例:

目标,示例服务

我希望能够调用各种类的已有函数:/exec &lt;functionName&gt; &lt;param1&gt; &lt;param2&gt;

假设您的应用程序具有以下现有类,这些类代表用户应该能够使用文本命令调用的服务:

struct Echo
{
    void WriteLine(const std::string& s) { std::cout << "WriteLine('"     << s << "');" << std::endl; }
    void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
    void WriteInt (int i)                { std::cout << "Write(int: "     << i <<  ");" << std::endl; }
    void WriteDbl (double d)             { std::cout << "Write(double: "  << d <<  ");" << std::endl; }
    void NewLine  ()                     { std::cout << "NewLine();"                    << std::endl; }
} echoService;

struct Admin
{
    void Shutdown(const std::string& reason, int retval) 
    { 
        std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
        // exit(retval);
    }
} adminService;

方法和解释(TL;DR?跳过这个)

那么我们如何将基于行的输入绑定到这个“接口”? 有两个工作

  • 解析输入
  • 评估输入

您一直强调为评估提供基础设施。但是,您会遇到不知道要传递哪些参数的问题。当然,您在解析时知道。您真的很想避免将参数存储在通用容器中。 (当然,你可以抛出 boost 选项,boost(递归)变体,一次boost任何一个,但让我们面对现实吧:这仍然是繁琐的工作)。

对于具有语义操作的解析器来说,这是一个绝佳的机会。 Lex/yacc、Coco/R C++、ANTLR 等等都支持它们。 Boost Spirit Qi 也是如此。

事不宜迟,这就是上述服务的完整、简约的行语法的样子:

parser = "/execute" > (
        (lit("WriteLine") > stringlit)
      | (lit("Write")    >> +(double_ | int_ | stringlit))
      | lit("NewLine")
      | (lit("Shutdown")  > (stringlit > -int_))

// stringlit is just a quoted string:    
stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];

注意:我决定向您展示如何在 /execute Write 调用中接受任意数量的各种类型的参数

添加语义操作以将其绑定到我们的 echoService 和 adminService 对象,我们让这个 REPL 引擎解析和评估一行:

void execute(const std::string& command)
{
    typedef std::string::const_iterator It;
    It f(command.begin()), l(command.end());

    if (!phrase_parse(f,l, "/execute" > (
            (lit("WriteLine")  
                > stringlit  [ bind(&Echo::WriteLine, ref(echoService), _1) ])
          | (lit("Write") >> +(
                  double_    [ bind(&Echo::WriteDbl,  ref(echoService), _1) ]
                | int_       [ bind(&Echo::WriteInt,  ref(echoService), _1) ]
                | stringlit  [ bind(&Echo::WriteStr,  ref(echoService), _1) ]
            ))
          | (lit("NewLine")  [ bind(&Echo::NewLine,   ref(echoService)) ])
          | (lit("Shutdown")  > (stringlit > (int_ | attr(0))) 
                             [ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
        ), space))
    {
        // handle error, see full code below
    }
}

现在我们要做的就是编写一个主循环:

int main()
{
    std::string command;
    while (std::getline(std::cin, command))
        execute(command);
}

这很简单,不是吗?


完整的工作示例程序

我在 github 1 上发布了这个程序的完整工作示例:https://gist.github.com/1314900

有

  • 添加了完整的错误处理/报告
  • 使用输入文件而不是 std::cin 来实现(易于测试)
  • 将帮助您开始这项工作(命名空间,包括)

您只需要 Boost。我用 g++ 4.6.1(没有特殊选项)和 Boost 1.47 对此进行了测试。对于以下测试输入(input.txt):

/execute WriteLine "bogus"
/execute Write     "here comes the answer: "
/execute Write     42
/execute Write     31415e-4
/execute Write     "that is the inverse of" 24 "and answers nothing"
/execute Shutdown  "Bye" 9
/execute Shutdown  "Test default value for retval"

演示程序的输出是

WriteLine('bogus');
Write(string: 'here comes the answer: ');
Write(double: 42);
Write(double: 3.1415);
Write(string: 'that is the inverse of');
Write(double: 24);
Write(string: 'and answers nothing');
Shutdown(reason: 'Bye', retval: 9)
Shutdown(reason: 'Test default value for retval', retval: 0)
  • 注意:我注释掉了 exit(...) 调用,以便演示如何在提供默认值 (attr(0)) 的同时使 retval 参数可选
  • 注意:双重31415e-4 如何正确打印为3.1415
  • 注意:/execute Write 的多个参数如何在对echoService.Write(...) 的单独调用中被转换,具体取决于输入参数的实际类型
  • 注意:如何完全忽略内联空格(字符串文字之外)的存在。您可以使用任意数量的制表符/空格

1 为了子孙后代的利益,github是否应该停止承载我的要点:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <fstream>

namespace qi  = boost::spirit::qi;
namespace phx = boost::phoenix;

///////////////////////////////////
// 'domain classes' (scriptables)
struct Echo
{
    void WriteLine(const std::string& s) { std::cout << "WriteLine('"     << s << "');" << std::endl; }
    void WriteStr (const std::string& s) { std::cout << "Write(string: '" << s << "');" << std::endl; }
    void WriteInt (int i)                { std::cout << "Write(int: "     << i <<  ");" << std::endl; }
    void WriteDbl (double d)             { std::cout << "Write(double: "  << d <<  ");" << std::endl; }
    void NewLine  ()                     { std::cout << "NewLine();"                    << std::endl; }
} echoService;

struct Admin
{
    void Shutdown(const std::string& reason, int retval) 
    { 
        std::cout << "Shutdown(reason: '" << reason << "', retval: " << retval << ")" << std::endl;
        // exit(retval);
    }
} adminService;

void execute(const std::string& command)
{
    typedef std::string::const_iterator It;
    It f(command.begin()), l(command.end());

    using namespace qi;
    using phx::bind;
    using phx::ref;

    rule<It, std::string(), space_type> stringlit = lexeme[ '"' >> *~char_('"') >> '"' ];

    try
    {
        if (!phrase_parse(f,l, "/execute" > (
                (lit("WriteLine")  
                    > stringlit  [ bind(&Echo::WriteLine, ref(echoService), _1) ])
              | (lit("Write") >> +(
                      double_    [ bind(&Echo::WriteDbl,  ref(echoService), _1) ] // the order matters
                    | int_       [ bind(&Echo::WriteInt,  ref(echoService), _1) ]
                    | stringlit  [ bind(&Echo::WriteStr,  ref(echoService), _1) ]
                ))
              | (lit("NewLine")  [ bind(&Echo::NewLine,   ref(echoService)) ])
              | (lit("Shutdown")  > (stringlit > (int_ | attr(0))) 
                                 [ bind(&Admin::Shutdown, ref(adminService), _1, _2) ])
            ), space))
        {
            if (f!=l) // allow whitespace only lines
                std::cerr << "** (error interpreting command: " << command << ")" << std::endl;
        }
    }
    catch (const expectation_failure<It>& e)
    {
        std::cerr << "** (unexpected input '" << std::string(e.first, std::min(e.first+10, e.last)) << "') " << std::endl;
    }

    if (f!=l)
        std::cerr << "** (warning: skipping unhandled input '" << std::string(f,l) << "')" << std::endl;
}

int main()
{
    std::ifstream ifs("input.txt");

    std::string command;
    while (std::getline(ifs/*std::cin*/, command))
        execute(command);
}

【讨论】:

  • 感谢所有这一切,我已经使用了一个解析器,它将输入分成各种 char* 数组,所以我得到了 /set 和 /get 命令,但我可能会使用这个对于更广泛的“使用参数执行任何其他功能”部分。你得到了一个很好的大赞:D,但我不能真正接受你的回答还,因为它没有回答原来的问题。
  • 啊,YX answering 的危险 :) 作为记录,我不介意,我很清楚我在回答其他问题,而不是你的明确问题。有时,我只是(a)尝试以我最有动力的方式提供帮助(b)我自己从中学习——如果只是灵巧的话。干杯!
【解决方案3】:

据我所知,你不能做你想做的事,因为std::function 会丢弃(几乎)传递的函数指针、函数对象或 lambda 的所有类型信息。它采用了一种称为type erasure 的技术,并且表面上完全忘记了传递给它的内容,只要它可以使用提供的参数进行调用。所以,绑定之后,看来你倒霉了。

但是,您可以自己提供该信息:

#include <functional>
#include <sstream>
#include <string>
#include <vector>

struct call_info{
  std::function<void(void)> func;
  std::string arg_type;
};

class MyClass{
  std::vector<call_info> _myVec;
  int _myInt;
  double _myDouble;

public:
  void foo1(int i);
  void foo2(double d);
  void bar();
  void execute(char* param);
};

void MyClass::bar(){
  call_info info;
  info.func = std::bind(&MyClass::foo1, this, &MyClass::_myInt);
  info.arg_type = "int";
  _myVector.push_back(info);
  info.func = std::bind(&MyClass::foo2, this, &MyClass::_myDouble);
  info.arg_type = "double";
  _myVector.push_back(info);
}

void MyClass::execute(char* param){
  call_info& info = _myVector[0];
  std::stringstream conv(param);

  if(info.arg_type == "int")
    conv >> _myInt;
  else if(info.arg_type == "double")
    conv >> _myDouble;
  info.func();
}

不像自动提供它那样干净,但据我所知,没有更好的方法(除了像 sehe 建议的那样完全改变你的实现)。

【讨论】:

    【解决方案4】:

    您的基本问题似乎是您的界面不够丰富。一旦你完成向你的向量添加东西(因此开始只使用你的函数对象多态),你应该不需要再次查看它们的具体类型。有几种方法可以解决这个问题:

    使用存储解析函数的附加回调:

    vector<boost::function<void(char*)> myParseVector;
    myParseVector.push_back(boost::bind(&MyClass::parseInt, this, MyClass::_myint);
    myParseVector.push_back(boost::bind(&MyClass::parseDouble, this, MyClass::_mydouble);
    

    ..或者,当然,通过自定义结构或一对将这两个回调放入同一个向量中。

    或者...使用支持parse 和execute 的自定义接口自己使用类型擦除!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-27
      • 2012-12-08
      • 2023-04-07
      • 2016-03-16
      • 1970-01-01
      • 2020-08-03
      • 1970-01-01
      • 2012-04-18
      相关资源
      最近更新 更多