简体   繁体   English

尾部呼叫优化和RAII可以共存吗?

[英]Can Tail Call Optimization and RAII Co-Exist?

I can't think of a true RAII language that also has tail call optimization in the specs, but I know many C++ implementations can do it as an implementation-specific optimization. 我不能想到在规范中也有尾调用优化的真正的RAII语言,但我知道许多C ++实现可以作为特定于实现的优化来实现。

This poses a question for those implementations that do: given that destructors are invoked at the end of a automatic variable's scope and not by a separate garbage collection routine, doesn't it violate TCO's constraint that a recursive call must be the last instruction at the end of a function? 这给那些实现的问题提出了一个问题:假设在自动变量范围的末尾调用析构函数而不是单独的垃圾收集例程,它是否违反了TCO的约束,即递归调用必须是最后一条指令。功能结束?

For example:- 例如:-

#include <iostream>

class test_object {
public:
    test_object() { std::cout << "Constructing...\n"; }
    ~test_object() { std::cout << "Destructing...\n"; }
};

void test_function(int count);

int main()
{
    test_function(999);
}

void test_function(int count)
{
    if (!count) return;
    test_object obj;
    test_function(count - 1);
}

"Constructing..." would be written 999 times and then "Destructing..." another 999 times. “构建......”将写入999次,然后“破坏......”再写999次。 Ultimately, 999 test_object instances would be automatically allocated before the unwind. 最终,在展开之前将自动分配999个test_object实例。 But assuming an implementation had TCO, would 1000 stack frames exist or just 1? 但假设一个实现有TCO,那么1000个堆栈帧是存在还是仅存在1?

Does the destructor after the recursive call collide with the defacto TCO implementation requirements? 递归调用之后的析构函数是否与事实上的TCO实现要求相冲突?

Taken at face-value, it would certainly seem like RAII works against TCO. 从面值来看,RAII肯定会对抗TCO。 However, remember that there are a number of ways in which the compiler can "get away with it", so to speak. 但是,请记住,编译器可以通过多种方式“远离它”,可以这么说。

The first and most obvious case is if the destructor is trivial, meaning that it is the default destructor (compiler-generated) and all the sub-objects have trivial destructors too, then the destructor is effectively non-existent (always optimized away). 第一个也是最明显的情况是,如果析构函数是微不足道的,这意味着它是默认的析构函数(编译器生成的),并且所有子对象也都有简单的析构函数,那么析构函数实际上是不存在的(总是被优化掉)。 In that case, TCO can be performed as usual. 在这种情况下,TCO可以照常执行。

Then, the destructor could be inlined (it's code is taken and put directly in the function as opposed to being called like a function). 然后,可以内联析构函数(它的代码被直接放入函数中,而不是像函数一样被调用)。 In that case, it boils down to just having some "clean-up" code after the return statement. 在这种情况下,它归结为在return语句后只有一些“清理”代码。 The compiler is allowed to re-order operations if it can determine that the end-result is the same (the "as-if" rule), and it will do so (in general) if the re-ordering leads to better code, and I would assume TCO is one of the considerations being applied by most compilers (ie, if it can re-order things such that the code becomes suitable for TCO, then it will do it). 如果编译器可以确定最终结果是相同的(“as-if”规则),则允许编译器重新排序操作,并且如果重新排序导致更好的代码,它将这样做(通常)我认为TCO是大多数编译器应用的考虑因素之一(即,如果它可以重新排序以使代码适合TCO,那么它就会这样做)。

And for the rest of the cases, where the compiler cannot be "smart enough" to do it on its own, then it becomes the responsibility of the programmer. 对于其他情况,编译器不能“足够智能”自己完成它,那么它就成了程序员的责任。 The presence of this automatic destructor call does make it a bit harder for the programmer to see the TCO-inhibiting clean-up code after the tail call, but it doesn't make any difference in terms of the ability of the programmer to make the function a candidate for TCO. 这种自动析构函数调用的存在确实让程序员在尾调用后看到TCO禁止清理代码变得有点困难,但它在程序员制作代码的能力方面没有任何区别。是TCO的候选人。 For example: 例如:

void nonRAII_recursion(int a) {
  int* arr = new int[a];
  // do some stuff with array "arr"
  delete[] arr;
  nonRAII_recursion(--a);  // tail-call
};

Now, a naive RAII_recursion implementation might be: 现在,一个天真的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.

But a wise programmer can still see that this won't work (unless the vector destructor is inlined, which is likely in this case), and can rectify the situation easily: 但是一个聪明的程序员仍然可以看到这不起作用(除非向量析构函数是内联的,在这种情况下很可能),并且可以很容易地纠正这种情况:

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
};

And I'm pretty sure you could demonstrate that there are essentially no cases where this kind of trick could not be used to ensure that TCO can be applied. 而且我很确定你可以证明基本上没有这种技巧不能用于确保可以应用TCO的情况。 So, RAII merely makes it a bit harder to see if TCO can be applied. 因此,RAII只是让人更难以确定是否可以应用TCO。 But I think programmers that are wise enough to design TCO-capable recursive calls are also wise enough to see those "hidden" destructor calls that would need to be forced to occur before the tail-call. 但我认为编程能够设计具有TCO功能的递归调用的程序员也足够明智地看到那些需要在尾调用之前强制发生的“隐藏”析构函数调用。

ADDED NOTE: Look at it this way, the destructor hides away some automatic clean-up code. 添加注意:以这种方式查看,析构函数隐藏了一些自动清理代码。 If you need the clean-up code (ie, non-trivial destructor), you will need it whether you use RAII or not (eg, C-style array or whatever). 如果你需要清理代码(即非平凡的析构函数),无论你是否使用RAII,你都需要它(例如,C风格的数组或其他)。 And then, if you want TCO to be possible, it must be possible to do the cleaning up before doing the tail-call (with or without RAII), and it is possible, then it is possible be force the RAII objects to be destroyed before the tail-call (eg, by putting them inside an extra scope). 然后,如果你想要TCO是可能的,那么必须有可能在进行尾调用之前进行清理(有或没有RAII),并且有可能,然后可能强制RAII对象被销毁在尾调用之前(例如,将它们放在额外​​的范围内)。

If the compiler perform the TCO then the order in which destructors are called is changed with respect to when it doesn't do TCO. 如果编译器执行TCO,则调用析构函数的顺序相对于不执行TCO的时间而改变。

If the compiler can prove that this reordering doesn't matter (eg if the destructor is trivial) then as per the as-if rule it can perform TCO. 如果编译器可以证明这种重新排序无关紧要(例如,如果析构函数是微不足道的)那么根据as-if规则它可以执行TCO。 However, in your example the compiler can't prove that and will not do TCO. 但是,在您的示例中,编译器无法证明并且不会执行TCO。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM