从表面上看,RAII 似乎对 TCO 有效。但是,请记住,编译器可以通过多种方式“摆脱它”,可以这么说。
第一个也是最明显的情况是如果析构函数是微不足道的,这意味着它是默认析构函数(编译器生成)并且所有子对象也有微不足道的析构函数,那么析构函数实际上不存在(总是优化掉)。在这种情况下,可以照常执行 TCO。
然后,可以内联析构函数(它的代码被直接放入函数中,而不是像函数一样被调用)。在这种情况下,归结为在 return 语句之后有一些“清理”代码。如果编译器可以确定最终结果相同(“as-if”规则),则允许编译器重新排序操作,并且如果重新排序导致更好的代码,它将这样做(通常),我会假设 TCO 是大多数编译器应用的考虑因素之一(即,如果它可以重新排序以使代码适合 TCO,那么它会这样做)。
而对于其余的情况,编译器不能“足够聪明”地自行完成,那么它就变成了程序员的责任。这种自动析构函数调用的存在确实使程序员在尾部调用之后更难看到抑制 TCO 的清理代码,但就程序员进行作为 TCO 的候选者。例如:
void nonRAII_recursion(int a) {
int* arr = new int[a];
// do some stuff with array "arr"
delete[] arr;
nonRAII_recursion(--a); // tail-call
};
现在,一个简单的RAII_recursion
实现可能是:
void RAII_recursion(int a) {
std::vector<int> arr(a);
// do some stuff with vector "arr"
RAII_recursion(--a); // tail-call
}; // arr gets destroyed here, not good for TCO.
但是聪明的程序员仍然可以看到这是行不通的(除非向量析构函数是内联的,在这种情况下很可能),并且可以轻松地纠正这种情况:
void RAII_recursion(int a) {
{
std::vector<int> arr(a);
// do some stuff with vector "arr"
}; // arr gets destroyed here
RAII_recursion(--a); // tail-call
};
而且我很确定您可以证明基本上没有不能使用这种技巧来确保可以应用 TCO 的情况。因此,RAII 只是让查看 TCO 是否可以应用变得更加困难。但我认为足够聪明地设计具有 TCO 能力的递归调用的程序员也足够聪明地看到那些需要在尾调用之前强制发生的“隐藏”析构函数调用。
补充说明:这样看,析构函数隐藏了一些自动清理代码。如果您需要清理代码(即,非平凡的析构函数),无论您是否使用 RAII(例如,C 样式数组或其他),都将需要它。然后,如果您希望 TCO 成为可能,则必须可以在进行尾调用之前进行清理(有或没有 RAII),并且有可能,然后有可能强制销毁 RAII 对象在尾调用之前(例如,通过将它们放在额外的范围内)。