假设我有这样的伪代码:

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


当前回答

如果你的代码像你的例子一样简单,并且你的语言支持短路计算,你可以尝试这样做:

StepA() && StepB() && StepC() && StepD();
DoAlways();

如果您将参数传递给函数并返回其他结果,因此您的代码不能以以前的方式编写,那么许多其他答案将更适合解决这个问题。

其他回答

在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

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

[...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++中都是自动化的。

如果你不喜欢goto和do {} while (0);循环和喜欢使用c++,你也可以使用临时lambda来达到同样的效果。

[&]() { // create a capture all lambda
  if (!executeStepA()) { return; }
  if (!executeStepB()) { return; }
  if (!executeStepC()) { return; }
}(); // and immediately call it

executeThisFunctionInAnyCase();

因为你也有……代码块…]在执行之间,我猜你有内存分配或对象初始化。通过这种方式,你必须关心在退出时你已经初始化的所有东西,如果你遇到问题,任何函数都会返回false,也要清理它。

在这种情况下,根据我的经验(当我使用CryptoAPI时),最好的方法是创建小类,在构造函数中初始化数据,在析构函数中反初始化数据。下一个函数类必须是前一个函数类的子类。如果出错-抛出异常。

class CondA
{
public:
    CondA() { 
        if (!executeStepA()) 
            throw int(1);
        [Initialize data]
    }
    ~CondA() {        
        [Clean data]
    }
    A* _a;
};

class CondB : public CondA
{
public:
    CondB() { 
        if (!executeStepB()) 
            throw int(2);
        [Initialize data]
    }
    ~CondB() {        
        [Clean data]
    }
    B* _b;
};

class CondC : public CondB
{
public:
    CondC() { 
        if (!executeStepC()) 
            throw int(3);
        [Initialize data]
    }
    ~CondC() {        
        [Clean data]
    }
    C* _c;
};

然后在你的代码中你只需要调用:

shared_ptr<CondC> C(nullptr);
try{
    C = make_shared<CondC>();
}
catch(int& e)
{
    //do something
}
if (C != nullptr)
{
   C->a;//work with
   C->b;//work with
   C->c;//work with
}
executeThisFunctionInAnyCase();

我想这是最好的解决方案,如果每次调用ConditionX初始化一些东西,分配内存等。最好确保所有东西都被清理干净。

在这种情况下,老式的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;
}

使用循环方法,在这种情况下,您将得到两级循环。