我正在构建一个递归下降解析器,我有两个构建列表的规则:
ValueList -> TOKEN_IDENTIFER TOKEN_QUOTE ValueListP
ValueListP -> ValueList
| %EPSILON%
现在我知道您可以轻松地将这两个规则优化为一个带有循环的规则,但我也知道编译器可以并且将在它看到它时执行尾调用优化。这是我目前的代码:
void Parser::grammarValueList( std::deque<std::unique_ptr<ValueNode>>& arg1 )
{
std::string var1 = m_currentToken.getValue().string;
if( acceptToken( Token::Type::TOKEN_IDENTIFIER ) )
{
std::string var2 = m_currentToken.getValue().string;
if( acceptToken( Token::Type::TOKEN_QUOTE ) )
{
arg1.push_back( std::unique_ptr<ValueNode>( new ValueNode( var1, var2 ) ) );
if( peekValueListP() )
{
return grammarValueListP( arg1 );
}
}
}
throw ParseException( "Error: did not expect \"" + m_currentToken.toString() + "\"" );
}
void Parser::grammarValueListP( std::deque<std::unique_ptr<ValueNode>>& arg1 )
{
if( peekValueList() )
{
return grammarValueList( arg1 );
}
else
{
return;
}
throw ParseException( "Error: did not expect \"" + m_currentToken.toString() + "\"" );
}
所以我有两个问题:
1)我提供的代码是否利用尾调优化?
2)即使一段代码确实利用了尾部调用优化,我们是否应该尝试在简单的情况下进行优化(删除递归并用循环替换)?
答案 0 :(得分:4)
不,grammarValueList
不执行尾调用。
问题是有两个std::string
类型的局部变量,它们有一个非平凡的析构函数。必须在方法返回之前调用那些析构函数,这是在调用grammarValueListP
之后调用的。所以对grammarValueListP
的调用不在尾部。
当然,有权访问析构函数定义的优化器可能会发现在不改变函数的情况下过早地破坏var1
和var2
是可能的。可见行为(假设它是可能的;它部分取决于ValueNode
构造函数中发生的事情)。但我不相信大多数C ++实现都会努力优化尾调用。
就个人而言,我使用循环,因为即使你设法消除析构函数调用,编译器仍然很可能无法找到TCO。从这个看似简单的例子中可以看出,C ++中的尾调用通常不像表面上看起来那么简单,并且说服优化器生成一个函数会非常困难。