假设我有这样的伪代码:
bool conditionA = executeStepA();
if (conditionA){
bool conditionB = executeStepB();
if (conditionB){
bool conditionC = executeStepC();
if (conditionC){
...
}
}
}
executeThisFunctionInAnyCase();
函数executeStepX当且仅当前一个成功时执行。
在任何情况下,executeThisFunctionInAnyCase函数都应该在最后被调用。
我在编程方面是一个新手,所以很抱歉提出一个非常基本的问题:有没有一种方法(例如在C/ c++中)以代码易读性为代价,避免长if链产生那种“金字塔式代码”?
我知道如果我们可以跳过executeThisFunctionInAnyCase函数调用,代码可以简化为:
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
但是约束是executeThisFunctionInAnyCase函数调用。
break语句可以以某种方式使用吗?
为了改进Mathieu的c++ 11答案并避免通过使用std::function而产生的运行时成本,我建议使用以下方法
template<typename functor>
class deferred final
{
public:
template<typename functor2>
explicit deferred(functor2&& f) : f(std::forward<functor2>(f)) {}
~deferred() { this->f(); }
private:
functor f;
};
template<typename functor>
auto defer(functor&& f) -> deferred<typename std::decay<functor>::type>
{
return deferred<typename std::decay<functor>::type>(std::forward<functor>(f));
}
这个简单的模板类将接受任何不需要任何参数就可以调用的函子,并且这样做不需要任何动态内存分配,因此更好地符合c++的抽象目标,没有不必要的开销。附加的函数模板用于简化模板参数推断的使用(类模板参数不可用)
使用的例子:
auto guard = defer(executeThisFunctionInAnyCase);
bool conditionA = executeStepA();
if (!conditionA) return;
bool conditionB = executeStepB();
if (!conditionB) return;
bool conditionC = executeStepC();
if (!conditionC) return;
正如Mathieu的答案,这个解决方案是完全异常安全的,在所有情况下都将调用executeThisFunctionInAnyCase。如果executeThisFunctionInAnyCase本身被抛出,析构函数被隐式地标记为noexcept,因此将发出对std::terminate的调用,而不是在堆栈展开期间引发异常。
在这种情况下,老式的C程序员使用goto。这是goto的一种用法,实际上是Linux样式指南鼓励的,它被称为集中函数exit:
int foo() {
int result = /*some error code*/;
if(!executeStepA()) goto cleanup;
if(!executeStepB()) goto cleanup;
if(!executeStepC()) goto cleanup;
result = 0;
cleanup:
executeThisFunctionInAnyCase();
return result;
}
有些人使用goto的方法是将body包装成一个循环并将其断开,但实际上这两种方法做的是同一件事。如果你只在executeStepA()成功时才需要一些其他的清理,那么goto方法会更好:
int foo() {
int result = /*some error code*/;
if(!executeStepA()) goto cleanupPart;
if(!executeStepB()) goto cleanup;
if(!executeStepC()) goto cleanup;
result = 0;
cleanup:
innerCleanup();
cleanupPart:
executeThisFunctionInAnyCase();
return result;
}
使用循环方法,在这种情况下,您将得到两级循环。
你可以把所有的if条件,按照你想要的格式放在它们自己的函数中,返回执行executeThisFunctionInAnyCase()函数。
从OP中的基本示例来看,条件测试和执行可以这样分离;
void InitialSteps()
{
bool conditionA = executeStepA();
if (!conditionA)
return;
bool conditionB = executeStepB();
if (!conditionB)
return;
bool conditionC = executeStepC();
if (!conditionC)
return;
}
然后被这样称呼;
InitialSteps();
executeThisFunctionInAnyCase();
如果c++ 11 lambda是可用的(OP中没有c++ 11标记,但它们可能仍然是一个选项),那么我们可以放弃单独的函数,并将其包装为lambda。
// Capture by reference (variable access may be required)
auto initialSteps = [&]() {
// any additional code
bool conditionA = executeStepA();
if (!conditionA)
return;
// any additional code
bool conditionB = executeStepB();
if (!conditionB)
return;
// any additional code
bool conditionC = executeStepC();
if (!conditionC)
return;
};
initialSteps();
executeThisFunctionInAnyCase();
在c++中(这个问题在C和c++中都有标记),如果你不能改变函数来使用异常,你仍然可以使用异常机制,如果你写一个像这样的小助手函数
struct function_failed {};
void attempt(bool retval)
{
if (!retval)
throw function_failed(); // or a more specific exception class
}
然后你的代码可以如下所示:
try
{
attempt(executeStepA());
attempt(executeStepB());
attempt(executeStepC());
}
catch (function_failed)
{
// -- this block intentionally left empty --
}
executeThisFunctionInAnyCase();
如果你喜欢花哨的语法,你可以通过显式强制转换来让它工作:
struct function_failed {};
struct attempt
{
attempt(bool retval)
{
if (!retval)
throw function_failed();
}
};
然后您可以将代码编写为
try
{
(attempt) executeStepA();
(attempt) executeStepB();
(attempt) executeStepC();
}
catch (function_failed)
{
// -- this block intentionally left empty --
}
executeThisFunctionInAnyCase();
对于c++ 11及以上版本,一个很好的方法可能是实现一个类似D的作用域(退出)机制的作用域退出系统。
实现它的一种可能的方法是使用c++ 11 lambdas和一些helper宏:
template<typename F> struct ScopeExit
{
ScopeExit(F f) : fn(f) { }
~ScopeExit()
{
fn();
}
F fn;
};
template<typename F> ScopeExit<F> MakeScopeExit(F f) { return ScopeExit<F>(f); };
#define STR_APPEND2_HELPER(x, y) x##y
#define STR_APPEND2(x, y) STR_APPEND2_HELPER(x, y)
#define SCOPE_EXIT(code)\
auto STR_APPEND2(scope_exit_, __LINE__) = MakeScopeExit([&](){ code })
这将允许你提前从函数返回,并确保你定义的任何清理代码总是在作用域退出时执行:
SCOPE_EXIT(
delete pointerA;
delete pointerB;
close(fileC); );
if (!executeStepA())
return;
if (!executeStepB())
return;
if (!executeStepC())
return;
宏实际上只是装饰。MakeScopeExit()可以直接使用。