在具有多个返回点的函数中清理的正确方法
Posted
技术标签:
【中文标题】在具有多个返回点的函数中清理的正确方法【英文标题】:Proper Way to Cleanup in a Function with Multiple Return Points 【发布时间】:2017-10-03 03:47:25 【问题描述】:我有一个递归搜索算法,我想在每次调用后清理我的指针。但是,我在这么多地方返回,在每个之前都放一个delete
或free
似乎很草率。
有没有更好的方法?我在函数返回时释放它们是否意味着我应该将它们分配在堆栈而不是堆中?
注意这是一个并行搜索(代码中没有显示),但是调用者永远不会在它的孩子之前返回。使用堆栈是否有任何额外的陷阱?
示例代码(这里不用担心算法):
//create a new struct state (using new), initialize and return (C style)
new_state()
free_list(state* node)//free a list
double minimax(state* node, state* bestState)
if (base_case)
return;
state* gb = new_state(); //single node
state* children = new_state(); //head of list
generate_children(children); //fill list
state* current = children; //traverse node
//recurse on child
double result = -minimax(current, gb);
if (case1)
free(gb);
free_list(children);
return;
if (case2)
//do stuff
while(current != NULL)
result = -minimax(current, gb);
if (case1)
free(gb);
free_list(children);
return;
if (case2)
//do stuff
current = current->next;
free(gb);
gb = NULL;
//More stuff (with children but not gb)
free_list(children);
return;
【问题讨论】:
这就是您使用 RAII 和智能指针的原因。他们清理自己,让您处理逻辑而不是清理。 @NathanOliver 我可以自定义智能指针析构函数吗?例如,我需要在退出范围时删除children
指向的整个列表。
当然。您可以给他们一个自定义删除器,当它超出范围时将运行该删除器。您也可以只构建一个封装列表的类类型并使用析构函数对其进行清理。如果你这样做了,那么智能指针将在超出范围时调用对象的析构函数。
@River,你也可以创建一个宏scope_exit
。我制作了a working version,但它仅适用于当前范围内的一个scope_exit
。无论如何,它是 RAII。
gb = NULL;
-- 函数末尾附近的这行代码不会真正影响任何事情。可以删除。
【参考方案1】:
但是,我在这么多地方返回,在每个地方都放一个 delete 或 free 似乎很草率。
是的。
有没有更好的办法?
是的。智能指针是一种更好的方法。但是,如果您不想放弃正在做的事情,并在继续之前学习如何使用智能指针(第一次可能很难),请继续往下阅读。
我在函数返回时释放它们是否意味着我应该将它们分配到堆栈而不是堆中?
是的,你可以这样做。它也会表现得更好。但是,如果您打算分配大量内存,它将不起作用。
注意这是一个并行搜索(代码中没有显示),但是调用者永远不会在它的孩子之前返回。使用堆栈是否有任何额外的陷阱?
陷阱是一样的。使用并行代码,您必须小心。
有很多方法可以避免这个问题。已经提到了智能指针和堆栈分配。
另一种方法是只有一个出口点。这有时会变得笨拙,因为例如,这意味着您必须在循环中断之前在循环中设置一个标志,以便知道它是成功终止还是由于错误而终止。
另一种方法是在函数 A 中分配指针,调用函数 B 来完成实际工作,(将分配的指针传递给它),然后一旦函数 B 返回到函数 A,释放指针。
【讨论】:
将children
放在堆栈上不是很危险吗?它指向堆上列表中的其他节点。如果它在作用域离开时被移除,列表的其余部分不会悬空吗?
尽管我们这么说很痛苦,但您也可以使用 goto cleanup
的好技巧。在所有退出点中,您使用goto cleanup
,然后在函数末尾创建一个cleanup
标签,然后执行清理代码并在一个地方全部返回。
@NathanOliver 实际上会很好地工作。唯一的问题是我在每个 return 语句中返回不同的变量。
@NathanOliver 我分享痛苦,也承认这实际上是一个可行的解决方案,在某些情况下它甚至可以产生更易于理解的代码。
@River 无论您当前的代码如何,您都在调用delete
。因此,您现在基本上正在做基于堆栈的 children
会做的事情。【参考方案2】:
这是一个小样本的 RAII:
首先,我们有一个 struct
来简单地存储您的物品。
struct FreeAll
state* gb;
state* children;
FreeAll(state* g, state* c) : gb(g), children(c)
~FreeAll() free(gb); free(children);
;
请注意,在销毁时,两个项目都会调用free()
。怎么用?
double minimax(state* node, state* bestState)
if (base_case)
return;
state* gb = new_state(); //single node
state* children = new_state(); //head of list
// Initialize our small RAII object with the above
// pointers
FreeAll fa(gb, children);
generate_children(children); //fill list
state* current = children; //traverse node
//recurse on child
double result = -minimax(current, gb);
if (case1)
return;
if (case2)
//do stuff
while(current != NULL)
result = -minimax(current, gb);
if (case1)
return;
if (case2)
//do stuff
current = current->next;
//More stuff (with children but not gb
return;
局部变量fa
是FreeAll
类型。当此本地超出范围时,将调用fa
的析构函数,该析构函数在存储在struct
中的两个指针上调用free
。还要注意在返回点没有任何代码来释放内存。这将由fa
在超出范围时完成。
请注意,这是一个简单的示例,没有提到的其他方法那么复杂,但它为您提供了 RAII 范例的基本要点。
【讨论】:
这里有一个错误,如果new_state()
在创建children
状态时引发异常,那么gb
状态就会被泄露。原始指针应仅用于引用其他人的数据。如果是您自己的数据,则应使用auto gb = make_unique<state>();
来确保您永远不会拥有不安全的数据。因为make_unique
遵循 RAII 模式,所以它将服务于请求的目的。【参考方案3】:
ScopeGuard 为您完成这项工作。
https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Scope_Guard
void your_function()
Scope_guard const final_action = []
free(gb);
free_list(children);;
// your code here
;
【讨论】:
以上是关于在具有多个返回点的函数中清理的正确方法的主要内容,如果未能解决你的问题,请参考以下文章
返回具有多个输入参数的函数的值以在同一类中具有多个参数的另一个函数中使用?
处理来自不同函数的多个返回数据集 python pandas