假设我有这样的伪代码:

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语句可以以某种方式使用吗?


当前回答

有几个回答暗示了我看到并使用过很多次的模式,尤其是在网络编程中。在网络堆栈中,经常有很长的请求序列,其中任何一个都可能失败并停止进程。

常见的模式是使用do {} while (false);

我使用宏while(false)使它做{}一次;常见的模式是:

do
{
    bool conditionA = executeStepA();
    if (! conditionA) break;
    bool conditionB = executeStepB();
    if (! conditionB) break;
    // etc.
} while (false);

这种模式相对容易阅读,并且允许使用能够正确销毁的对象,还避免了多次返回,从而使步进和调试更容易一些。

其他回答

你也可以这样做:

bool isOk = true;
std::vector<bool (*)(void)> funcs; //vector of function ptr

funcs.push_back(&executeStepA);
funcs.push_back(&executeStepB);
funcs.push_back(&executeStepC);
//...

//this will stop at the first false return
for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();

通过这种方式,您可以获得最小的线性增长大小,每次调用+1行,并且易于维护。


编辑:(谢谢@Unda)我不太喜欢,因为在我看来你失去了能见度:

bool isOk = true;
auto funcs { //using c++11 initializer_list
    &executeStepA,
    &executeStepB,
    &executeStepC
};

for (auto it = funcs.begin(); it != funcs.end() && isOk; ++it) 
    isOk = (*it)();
if (isOk)
 //doSomeStuff
executeThisFunctionInAnyCase();

关于当前的代码示例,本质上是第二个问题,

[...block of code...]
bool conditionA = executeStepA();    
if (conditionA){
    [...block of code...]
    bool conditionB = executeStepB();
    if (conditionB){
        [...block of code...]
        bool conditionC = executeStepC();
        if (conditionC){
            ...other checks again...
        }
    }
}

executeThisFunctionInAnyCase();

除了将函数结果存储在变量中之外,这是典型的C代码。

如果布尔函数导致信号失败,那么c++的方法是使用异常,并将其编码为

struct Finals{ ~Finals() { executeThisFunctionInAnyCase(); } };

Finals finals;
// [...block of code...]
executeStepA();
// [...block of code...]
executeStepB();
// [...block of code...]
executeStepC();
//...other checks again...

然而,根据实际问题的不同,细节可能会有很大差异。

当我需要这样的通用最终操作时,我通常使用通用范围保护类,而不是当场定义一个自定义结构。作用域保护是由Petru Marginean为c++ 98发明的,然后使用临时生命周期扩展技巧。在c++ 11中,一般的范围保护类可以基于提供lambda表达式的客户端代码简单地实现。

在问题的最后,你建议了一个很好的C方法来做到这一点,即使用break语句:

for( ;; ) // As a block one can 'break' out of.
{
    // [...block of code...]
    if( !executeStepA() ) { break; }
    // [...block of code...]
    if( !executeStepB() ) { break; }
    // [...block of code...]
    if( !executeStepC() ) { break; }
    //...other checks again...
    break;
}
executeThisFunctionInAnyCase();

或者,对于C,将代码块中的代码重构为一个单独的函数,并使用return而不是break。因为它支持嵌套循环或开关,所以这更清楚也更通用。然而,你问的是休息。

与基于异常的c++方法相比,这种方法依赖于程序员记得检查每个函数的结果,并做正确的事情,这两者在c++中都是自动化的。

在c++中,实际上有一种方法可以延迟操作:使用对象的析构函数。

假设你可以访问c++ 11:

class Defer {
public:
    Defer(std::function<void()> f): f_(std::move(f)) {}
    ~Defer() { if (f_) { f_(); } }

    void cancel() { f_ = std::function<void()>(); }

private:
    Defer(Defer const&) = delete;
    Defer& operator=(Defer const&) = delete;

    std::function<void()> f_;
}; // class Defer

然后使用这个工具:

int foo() {
    Defer const defer{&executeThisFunctionInAnyCase}; // or a lambda

    // ...

    if (!executeA()) { return 1; }

    // ...

    if (!executeB()) { return 2; }

    // ...

    if (!executeC()) { return 3; }

    // ...

    return 4;
} // foo

对于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()可以直接使用。

在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();