我刚刚编写了一个递归函数,我意识到我在函数中使用的所有变量都将保留在内存中,直到递归中断为止。如果我递归很多次或为后续递归函数调用中未使用的变量分配大量内存,这是否会导致大量浪费内存使用?
E.g。在下文中,只有vec2
用于以下递归,而temp_int
和temp_vec
将不必要地继续占用内存。
int recurse(std::vector<int> arg_vec) {
int temp_int i;
std::vector<int> temp_vec;
std::vector<int> vec2;
//... do some processing with arg_vec and temp_vec and result is stored in vec2
recurse(vec2)
return if (some condition met);
}
我应该使用新命令分配所有内存并在函数调用之前删除它们吗?或者是否有其他方法来处理这个
答案 0 :(得分:6)
您可以使用范围大括号指定范围。范围内声明的任何内容都会在范围的末尾被销毁。
int recurse(std::vector<int> arg_vec) {
int temp_int i;
std::vector<int> vec2;
{
std::vector<int> temp_vec;
//... do some processing with arg_vec and temp_vec and result is stored in vec2
} // temp_vec is destructed here. vec2 is not because it is outside this scope.
recurse(ec2)
return if (some condition met);
}
答案 1 :(得分:5)
通常,在这种情况下你所做的是 tail-recursion ,它允许编译器优化它。
这意味着,递归函数最后做的就是调用自身。如果您有进一步的说明,我不知道优化有多好。
编辑(澄清)
int foo(int i) {
if (stop_condition(i))
return stuff;
// fancy computation
return foo(bar);
}
答案 2 :(得分:1)
应用程序往往拥有比堆栈更多的堆内存,因此您可以分配而不是使用自动存储。这是你在使用std :: vector时已经在做的事情。但分配可能会很慢。为了充分利用这两个方面,请使用迭代重写递归函数。然后,您可以预先分配一次,并在用完预分配空间的情况下重新分配。
答案 3 :(得分:0)
在输入递归之前,你可以释放temp_vec的数组内存。
不通过
temp_vec.clear();
因为标准不保证clear()释放分配的数组内存
std::swap(temp_vec. std::vector<int>());
无论如何,在该示例中使用temp_vec的另一个范围更有用,因为在离开范围之后,向量对象本身的空间在堆栈上被释放。见其他答案。
顺便说一句,考虑而不是使用按值调用
int recurse(std::vector<int> arg_vec) {
通过引用调用以避免不必要的向量复制:
int recurse(const std::vector<int> &arg_vec) {
答案 4 :(得分:0)
Tail calls可以进行优化,根本不需要额外的堆栈空间,使它们像迭代版本一样快速,并且(大致取决于其他因素)。所有现代C和C ++编译器都执行此优化。即使它不是尾调用,编译器也可以在没有重复的情况下计算出优化版本。 In this answer,有人证明gcc即使在-O2上也会在快速迭代中变成一个天真的因子实现。
答案 5 :(得分:0)
你可以用new来分配所有内容,但是将执行实际处理的代码移动到具有自己的本地的另一个函数可能更容易。像这样:
void subfunction(vector arg_vec, vector result_vec) { int temp_int; vector temp_vec; // do stuff return; } int recurse(vector arg_vec) { vector vec2; subfunction(arg_vec, vec2); recurse(vec2); }
作为奖励,现在你将递归转换为循环的一半,如果你的递归足够,堆栈空间可能是一个问题,这是处理事情的更好方法。
答案 6 :(得分:0)
更改为手动分配几个std::vector
并不会产生太大影响。向量实际上是一个非常小的数据结构(通常是两个size_t
s加一个指针) - 实际数据的存储已经动态分配。因此,动态分配std::vector
只能为每个堆栈帧节省两个size_t
s。如果你正好处于工作与否的边缘,这可能会产生真正的影响,但我猜这很少见(如果你那么接近,你应该首先考虑其他可能性。) / p>