#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发出与我正在尝试的多态性兼容的引用(如果是,如何),这是我尝试完成的最佳方法(即表示可执行对象的列表)解析的命令及其参数)?
解决方法
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