c – 如何在boost :: spirit :: qi解析器中使用多态属性?

我希望我的boost :: spirit-based解析器能够解析文件,将解析后的规则转换为不同的类型,并发出包含它找到的所有匹配项的向量.作为属性发出的所有类型都应该从基类型继承,例如:
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapt_struct.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/foreach.hpp>

struct CommandBase
{
   virtual void commandAction()
   {
     std::cout << "This is a base command. You should never see this!" << std::endl;
     //Boost::spirit seems to get mad if I make this purely virtual. Clearly I'm doing it wrong.
   }
};

struct CommandTypeA : public CommandBase
{
   int valueA;
   int valueB;
   virtual void commandAction()
   {
      std::cout << "CommandType A! ValueA: " << valueA << " ValueB: " << valueB << std::endl;
   }

};

struct CommandTypeB : public CommandBase
{
   double valueA;
   std::vector<char> valueB;
   virtual void commandAction()
   {
      std::cout << "CommandType B! valueA: " << valueA << " string: " << std::string(valueB.begin(),valueB.end()) << std::endl;
   }
};
struct CommandTypeC : public CommandBase
{
  //Represents a sort of "subroutine" type where multiple commands can be grouped together
  std::vector<char> labelName;
  std::vector<boost::shared_ptr<CommandBase> > commands;
  virtual void commandAction()
  {
      std::cout << "Subroutine: " << std::string(labelName.start(),labelName.end())
                << " has " << commands.size() << " commands:" << std::endl;
      BOOST_FOREACH(boost::shared_ptr<CommandBase> c,commands)
      {
           c->commandAction();
      }          
  }
};

现在,我尝试了解析器代码

namespace ascii = boost::spirit::ascii;
namespace qi = boost::spirit::qi;
using qi::lit_;

BOOST_FUSION_ADAPT_STRUCT(
   CommandTypeA,(int,valueA)
   (int,valueB)
)

BOOST_FUSION_ADAPT_STRUCT(
   CommandTypeB,(double,valueA)
   (std::vector<char>,valueB)
)

BOOST_FUSION_ADAPT_STRUCT(
   CommandTypeC,(std::vector<char>,labelName)
   (std::vector<boost::shared_ptr<CommandBase> >,commands)
)

template<typename Iterator,typename Skipper = ascii::space_type>
struct CommandParser : qi::grammar<Iterator,std::vector<boost::shared_ptr<CommandBase> >(),Skipper>
{
   public:
   CommandParser() : CommandParser()::base_type(commands)
   {
      CommandARule = qi::int_ >> qi::int_ >> lit("CMD_A");
      CommandBRule = qi::int_ >> +(qi::char_) >> lit("CMD_B");
      Commandcrule = qi::char_(':') >> lexeme[+(qi::char_ - ';' - ascii::space) >> +ascii::space] >> commands >> qi::char_(';');

      commands = +(CommandARule | CommandBRule | Commandcrule);
   }
   protected:
   qi::rule<Iterator,boost::shared_ptr<CommandTypeA>,Skipper> CommandARule;
   qi::rule<Iterator,boost::shared_ptr<CommandTypeB>,Skipper> CommandBRule;
   qi::rule<Iterator,boost::shared_ptr<CommandTypeC>,Skipper> Commandcrule;
   qi::rule<Iterator,std::vector<boost::shared_ptr<CommandBase> >,Skipper> commands;

};


std::vector<boost::shared_ptr<CommandBase> > commandList;
bool success = qi::phrase_parse(StartIterator,EndIterator,CommandParser,ascii::space,commandList);

BOOST_FOREACH(boost::shared_ptr<CommandBase> c,commandList)
{
    c->commandAction();
}

现在,这段代码肯定不会编译,但是我希望它能够解决我正在尝试做的事情.

主要的问题是qi :: rules似乎想要发出实际的结构,而不是它的引用.

我的问题是:

是否有可能强制qi :: rule发出与我正在尝试的多态性兼容的引用(如果是,如何),这是我尝试完成的最佳方法(即表示可执行对象的列表)解析的命令及其参数)?

解决方法

Spirit对编译时多态性更友好
typedef variant<Command1,Command2,Command3> Command;

但是,让我们假设你真的想要做老式的多态性事情……

然而,在解析过程中即时刷新多态对象是一种可靠的方法

>使用语义动作使解析器变得臃肿
>在语法规则中的反向跟踪上创建大量内存泄漏
>使解析非常慢(因为你有各种动态分配方式).
>最糟糕的是,即使您实际上没有将属性引用传递给顶级解析API,也不会对其进行优化. (通常,所有属性处理“神奇地”在编译时蒸发,这对输入格式验证非常有用)

因此,您需要为基本命令类或派生的对象创建持有者.使支架满足RuleOfZero并通过类型擦除获得实际值.

(除了解决“偶然”复杂性并限制内存回收之外,这种抽象的一个好处是您仍然可以选择静态处理存储,因此可以在堆分配中节省大量时间.)

我会看看你的样本,看看我能否快速展示它.

这就是我对’holder’类的意思(向CommandBase添加一个虚拟析构函数!):

struct CommandHolder
{
    template <typename Command> CommandHolder(Command cmd) 
        : storage(new concrete_store<Command>{ std::move(cmd) }) { }

    operator CommandBase&() { return storage->get(); }
  private:
    struct base_store {
        virtual ~base_store() {}; 
        virtual CommandBase& get() = 0;
    };
    template <typename T> struct concrete_store : base_store {
        concrete_store(T v) : wrapped(std::move(v)) { }
        virtual CommandBase& get() { return wrapped; }
      private:
        T wrapped; 
    };

    boost::shared_ptr<base_store> storage;
};

正如您所看到的,我在这里为simples所有权语义选择了unique_ptr(一种变体可以避免一些分配开销作为后来的优化).我无法使Unique_ptr与Spirit一起工作,因为Spirit根本就不知道移动. (精神X3将是).

我们可以基于此持有者轻松实现类型擦除的AnyCommand:

struct AnyCommand : CommandBase
{
    template <typename Command> AnyCommand(Command cmd) 
        : holder(std::move(cmd)) { }

    virtual void commandAction() override { 
        static_cast<CommandBase&>(holder).commandAction();
    }
  private:
    CommandHolder holder;
};

因此,现在您可以将任何命令“分配”给AnyCommand并通过持有者“多态”使用它,即使持有者和AnyCommand具有完美的价值语义.

这个示例语法将:

CommandParser() : CommandParser::base_type(commands)
{
    using namespace qi;
    CommandARule = int_    >> int_           >> "CMD_A";
    CommandBRule = double_ >> lexeme[+(char_ - space)] >> "CMD_B";
    Commandcrule = ':' >> lexeme [+graph - ';'] >> commands >> ';';

    command  = CommandARule | CommandBRule | Commandcrule;
    commands = +command;
}

规则定义为:

qi::rule<Iterator,CommandTypeA(),Skipper> CommandARule;
qi::rule<Iterator,CommandTypeB(),Skipper> CommandBRule;
qi::rule<Iterator,CommandTypeC(),Skipper> Commandcrule;
qi::rule<Iterator,AnyCommand(),Skipper> command;
qi::rule<Iterator,std::vector<AnyCommand>(),Skipper> commands;

这是值语义和运行时多态的非常令人愉快的组合:)

测试主要是

int main()
{
    std::string const input =
        ":group             \n"
        "     3.14  π CMD_B \n"
        "     -42  42 CMD_A \n"
        "     -inf -∞ CMD_B \n"
        "     +inf +∞ CMD_B \n"
        ";                  \n"
        "99 0 CMD_A";

    auto f(begin(input)),l(end(input));

    std::vector<AnyCommand> commandList;
    CommandParser<std::string::const_iterator> p;
    bool success = qi::phrase_parse(f,l,p,qi::space,commandList);

    if (success) {
        BOOST_FOREACH(AnyCommand& c,commandList) {
            c.commandAction();
        }
    } else {
        std::cout << "Parsing Failed\n";
    }

    if (f!=l) {
        std::cout << "Remaining unparsed input '" << std::string(f,l) << "'\n";
    }
}

打印:

Subroutine: group has 4 commands:
CommandType B! valueA: 3.14 string: π
CommandType A! ValueA: -42 ValueB: 42
CommandType B! valueA: -inf string: -∞
CommandType B! valueA: inf string: +∞
CommandType A! ValueA: 99 ValueB: 0

查看全部Live On Coliru

相关文章

对象的传值与返回说起函数,就不免要谈谈函数的参数和返回值...
从实现装饰者模式中思考C++指针和引用的选择最近在看...
关于vtordisp知多少?我相信不少人看到这篇文章,多半是来自...
那些陌生的C++关键字学过程序语言的人相信对关键字并...
命令行下的树形打印最近在处理代码分析问题时,需要将代码的...
虚函数与虚继承寻踪封装、继承、多态是面向对象语言的三大特...