如何将多态属性与boost::spirit::qi解析器一起使用
How can I use polymorphic attributes with boost::spirit::qi parsers?
我希望基于boost::spirit的解析器能够解析文件,将解析的规则转换为不同的类型,并发出一个包含它找到的所有匹配项的向量。作为属性发出的所有类型都应该从基本类型继承,例如:
#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::规则似乎想要发出实际的结构,而不是对它的引用
因此,我的问题是:
是否可以像我尝试的那样强制qi::规则发出多态性兼容的引用(如果是,如何),这是否是我尝试实现的最佳方法(即表示解析命令及其参数的可执行对象列表)
Spirit对compiletime多态性更友好
typedef variant<Command1, Command2, Command3> Command;
但是,假设你真的想做老式的多态性。。。
然而,在解析过程中动态地更新多态对象是的一种很好的方法
- 使语法分析器充满语义操作
- 在语法规则的回溯中创建大量内存泄漏
- 使解析速度慢得惊人(因为您有各种各样的动态分配)
- 最糟糕的是,这些都不会被优化掉,即使您实际上没有将属性引用传递到顶级
parse
API中。(通常,所有属性处理都会在编译时"神奇地"蒸发,这对输入格式验证非常有用)
因此,您需要为基本命令类或派生命令类的对象创建一个holder。使持有者满足RuleOfZero并通过类型擦除获得实际值。
(除了解决"意外"的复杂性和限制w.r.t.内存回收之外,这种抽象的好处是你仍然可以选择静态处理存储,这样你就可以在堆分配中节省[很多]时间。)
我会看看你的样品,看看我是否能很快证明。
以下是我对"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;
};
正如您所看到的,为了简化所有权语义,我选择了。我无法让unique_ptr
(variant
将避免一些分配开销,作为稍后的优化)unique_ptr
与Spirit一起工作,因为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,并通过holder"多态地"使用它,即使holder和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 failedn";
}
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
全部查看在Coliru上直播
- 如何将enable-if与模板参数和参数包一起使用
- 如何将PERF_AMPLE_READ与mmap一起使用
- 如何将两个不同矢量的同一位置的两个元素组合在一起
- 如何将C++中的库和头与MinGW一起使用
- 将--whole archive链接器选项与CMake和具有其他库依赖项的库一起使用
- 为什么我不能将 rand() 与数组的大小一起使用?
- 要与"if constexpr"一起使用的编译时消息(在预处理器之后)
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 将fold表达式与std::一起用于两个元组
- spdlog标头仅与外部fmt一起使用.spdlog错误:'内部':不是'fmt'
- 将 std::allocate_shared 与多态资源分配器一起使用
- 为什么常量词在重载运算符中不与 ostream 对象一起使用<<?
- 将 OpenCV 与 CMAKE 中的项目一起构建为第三方库的正确方法
- 将 exprtk 与自定义类的对象一起使用
- 将 std::set 与基于键的比较器一起使用
- 将 C++ 类与 Rcpp 一起使用,从 C 或 R 修改它
- 如何将 Eigen::Ref 与 pybind11 一起使用?
- boost :: Spirit :: Qi-与语法结构中的实例成员一起工作
- 将 boost::spirit::qi::p hrase_parse() 与 qi::grammar 一起使用时出错
- 如何将多态属性与boost::spirit::qi解析器一起使用