我遇到了一个稍微不寻常的问题。请考虑以下代码:
class parser
{
lexer lex;
public:
node_ptr parse(const std::string& expression)
{
lex.init(expression.begin(), expression.end());
// ...
// call some helper methods
// return the result
}
private:
// lots of small helper methods, many of them accessing lex
};
parse方法使用init
方法初始化词法分析器。在此之前,词法分析器处于不可用的“默认”状态。通常,人们应该在施工期间初步建立一个成员,所以为什么我不这样做呢:
class parser
{
lexer lex;
public:
parser(const std::string& expr) : lex(expr.begin(), expr.end()) {}
node_ptr parse()
{
// call some helper methods
// return the result
}
// ...
};
首先,这意味着客户端可以多次调用解析方法,这没有多大意义。
其次,更重要的是,它很容易导致严重的终身问题:
parser my_parser("1 * 2 + 3 * 4");
auto root = my_parser.parse();
在上面的代码中,词法分析器将使用一个临时字符串对象进行初始化,该对象不再存在于行尾,因此在下一行中调用parse
方法将调用未定义的行为。
由于这两个原因,我真的想用同样的方法初始化和解析。不幸的是,我不能在构造函数中执行它,因为我需要返回一个结果,而构造函数不能返回结果。
从技术上讲,可以在parse
方法中构造词法分析器,如果我也相应地更改构造函数和析构函数,则可以在之后销毁它:
class parser
{
static std::string dummy;
lexer lex;
public:
parser() : lex(dummy.begin(), dummy.end())
{
lex.~lexer();
}
node_ptr parse(const std::string& expression)
{
new(&lex) lexer(expression.begin(), expression.end());
// call some helper methods
lex.~lexer();
// return the result
}
~parser()
{
new(&lex) lexer(dummy.begin(), dummy.end());
}
// ...
};
但这是迄今为止我在很长一段时间内编写的最丑陋的代码。它也不例外;如果帮助方法抛出怎么办?实际上,这正是遇到解析错误时发生的情况。
那么我应该如何解决这个问题呢?在parse
内使用本地词法分析器并指向lexer*
成员?使用boost::optional<lexer>
会员?或者我应该使用init
方法?或者我应该在构造函数中进行解析并抛出包含所需结果的“期望”?
答案 0 :(得分:3)
我绝对不会做你的第二个例子。更好的是在lexer
中构建Parse()
,并存储指针或boost::optional
。但是如果你想允许这个,那么你的助手函数必须在继续之前检查词法分析器是否有效。对我来说似乎很乱。
更好的是让Parse
成为一个独立的功能。我设想这对呼叫者更敏感,并解决你的问题:
void parser_helper(lexer& lex)
{
...
}
node_ptr Parse(const std::string& inp)
{
lexer lex(inp);
...
parser_helper(lex);
...
return ret;
}
或者如果你有更多的州传递......
class parser_helper
{
lexer lex;
... other state here
public:
parser_helper(const std::string& inp) :
lex(inp)
{
}
... helper functions here.
void helper_function() { }
}
node_ptr Parse(const std::string& inp)
{
parser_helper helper(inp);
...
helper.helper_function();
...
return ret;
}
无论哪种方式,词法分析器应该只是Parse
函数中的自动变量。
这个想法是调用者期望的接口只是一个函数。不需要让调用者处理类,只是因为Parse
的内部具有状态/辅助函数。
答案 1 :(得分:2)
您没有理由parse
(事实上,lex
)不应该是简单的函数。此外,您没有说明parse
采用该表达式,或采用初始化lexer
。
编辑:
或者只是在解析堆栈上将它们创建为lambdas。但是,正如我所说,我可以看到解析器存在的必要性。但它似乎不需要存在于解析自身的成员方法之外,质疑为什么不重构该方法以超出类。像
这样的东西class parser {
lexer l;
// stuff
};
node_ptr parse(...) {
parser p(...);
return p();
}
答案 2 :(得分:0)
在上面的代码中,词法分析器将使用暂时不存在于行尾的字符串对象进行初始化,因此在下一行中调用parse方法将调用未定义的行为。
这没有意义。您可以复制临时字符串并在lexer
中使用它。
答案 3 :(得分:0)
在解析中使用本地词法分析器并使lexer *成员指向它?
这得到我的投票。这样你就可以完全控制它的生命周期。