干净的方法在C中做多个撤消

dar*_*aud 40 c

有人可能会说一些关于异常的事情......但是在C中,有什么其他方法可以干净利落地清楚地完成以下操作并且不需要重复这么多代码?

if (Do1()) { printf("Failed 1"); return 1; }
if (Do2()) { Undo1(); printf("Failed 2"); return 2; }
if (Do3()) { Undo2(); Undo1(); printf("Failed 3"); return 3; }
if (Do4()) { Undo3(); Undo2(); Undo1(); printf("Failed 4"); return 4; }
if (Do5()) { Undo4(); Undo3(); Undo2(); Undo1(); printf("Failed 5"); return 5; }
Etc...
Run Code Online (Sandbox Code Playgroud)

这可能是使用gotos的一种情况.或者可能是多个内部功能......

lik*_*kle 47

是的,在这种情况下使用goto是很常见的,以避免重复自己.

一个例子:

int hello() {
  int result;

  if (Do1()) { result = 1; goto err_one; }
  if (Do2()) { result = 2; goto err_two; }
  if (Do3()) { result = 3; goto err_three; }
  if (Do4()) { result = 4; goto err_four; }
  if (Do5()) { result = 5; goto err_five; }

  // Assuming you'd like to return 0 on success.
  return 0;

err_five:
  Undo4();
err_four:
  Undo3();
err_three:
  Undo2();
err_two:
  Undo1();
err_one:
  printf("Failed %i", result); 
  return result;
}
Run Code Online (Sandbox Code Playgroud)

与往常一样,您可能还希望保持较小的功能并以有意义的方式将操作批量组合在一起,以避免出现大的"撤消代码".

  • @Lundin这里没有代码重复.请不要误解具体的例子. (11认同)
  • @Lundin似乎你不明白这种方法是如何工作的.如果你必须添加另一个案例,则*no*需要更改*其他*else.这就是重点. (11认同)
  • @Lundin我同意.这个答案假设Do和Undo功能在现实中可能有不同的签名,这在实践中可能更常见. (3认同)

Bla*_*aze 18

这可能是使用gotos的一种情况.

当然,我们试试吧.这是一个可能的实现:

#include "stdio.h"
int main(int argc, char **argv) {
    int errorCode = 0;
    if (Do1()) { errorCode = 1; goto undo_0; }
    if (Do2()) { errorCode = 2; goto undo_1; }
    if (Do3()) { errorCode = 3; goto undo_2; }
    if (Do4()) { errorCode = 4; goto undo_3; }
    if (Do5()) { errorCode = 5; goto undo_4; }

undo_5: Undo5();    /* deliberate fallthrough */
undo_4: Undo4();
undo_3: Undo3();
undo_2: Undo2();
undo_1: Undo1();
undo_0: /* nothing to undo in this case */

    if (errorCode != 0) {
        printf("Failed %d\n", errorCode);
    }
    return errorCode;
}
Run Code Online (Sandbox Code Playgroud)

  • @Lundin根本没有束缚.你正在做假设,它们甚至不符合那里的实际代码. (3认同)
  • -1对于那些认为"goto"绝对邪恶的人来说是可能的,只要它被滥用就不行了. (2认同)
  • @Lundin不是. (2认同)

Tom*_*m's 14

如果您的功能具有相同的签名,您可以执行以下操作:

bool Do1(void) { printf("function %s\n", __func__); return true;}
bool Do2(void) { printf("function %s\n", __func__); return true;}
bool Do3(void) { printf("function %s\n", __func__); return false;}
bool Do4(void) { printf("function %s\n", __func__); return true;}
bool Do5(void) { printf("function %s\n", __func__); return true;}

void Undo1(void) { printf("function %s\n", __func__);}
void Undo2(void) { printf("function %s\n", __func__);}
void Undo3(void) { printf("function %s\n", __func__);}
void Undo4(void) { printf("function %s\n", __func__);}
void Undo5(void) { printf("function %s\n", __func__);}


typedef struct action {
    bool (*Do)(void);
    void (*Undo)(void);
} action_s;


int main(void)
{
    action_s actions[] = {{Do1, Undo1},
                          {Do2, Undo2},
                          {Do3, Undo3},
                          {Do4, Undo4},
                          {Do5, Undo5},
                          {NULL, NULL}};

    for (size_t i = 0; actions[i].Do; ++i) {
        if (!actions[i].Do()) {
            printf("Failed %zu.\n", i + 1);
            for (int j = i - 1; j >= 0; --j) {
                actions[j].Undo();
            }
            return (i);
        }
    }

    return (0);
}
Run Code Online (Sandbox Code Playgroud)

您可以更改其中一个Do函数的返回值以查看它的反应:)


alk*_*alk 8

为了完整性,有点混淆:

int foo(void)
{
  int rc;

  if (0
    || (rc = 1, do1()) 
    || (rc = 2, do2()) 
    || (rc = 3, do3()) 
    || (rc = 4, do4()) 
    || (rc = 5, do5())
    || (rc = 0)
  ) 
  {
    /* More or less stolen from Chris' answer: 
       https://stackoverflow.com/a/53444967/694576) */
    switch(rc - 1)
    {
      case 5: /* Not needed for this example, but left in in case we'd add do6() ... */
        undo5();

      case 4:
        undo4();

      case 3:
        undo3();

      case 2:
        undo2();

      case 1:
        undo1();

      default:
        break;
    }
  }

  return rc;
}
Run Code Online (Sandbox Code Playgroud)

  • 现在我们所缺少的是一个带有Duff设备的版本,我们将准备好将这个帖子提交给[IOCCC](https://www.ioccc.org/):) (13认同)
  • 请停止这种疯狂. (5认同)
  • @Acorn:为什么?如何使用逗号运算符的好例子...... ;-) (3认同)
  • 其他解决方案同样易于自动生成(如果您正在谈论代码生成). (2认同)

Aco*_*orn 5

使用gotoC语言来管理清理

例如,检查Linux 内核编码风格

使用gotos的理由是:

  • 无条件语句更容易理解,并且减少了嵌套
  • 防止在进行修改时不更新各个出口点而导致的错误
  • 节省编译器的工作以优化冗余代码;)

例子:

int fun(int a)
{
    int result = 0;
    char *buffer;

    buffer = kmalloc(SIZE, GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    if (condition1) {
        while (loop1) {
            ...
        }
        result = 1;
        goto out_free_buffer;
    }

    ...

out_free_buffer:
    kfree(buffer);
    return result;
}
Run Code Online (Sandbox Code Playgroud)

在您的特定情况下,它可能如下所示:

int f(...)
{
    int ret;

    if (Do1()) {
        printf("Failed 1");
        ret = 1;
        goto undo1;
    }

    ...

    if (Do5()) {
        printf("Failed 5");
        ret = 5;
        goto undo5;
    }

    // all good, return here if you need to keep the resources
    // (or not, if you want them deallocated; in that case initialize `ret`)
    return 0;

undo5:
    Undo4();
...
undo1:
    return ret;
}
Run Code Online (Sandbox Code Playgroud)

  • 这在某种程度上是可以接受的 goto 用法——它是来自 BASIC 的一种模式,称为“on error goto”。我希望人们不要停止思考,而是更进一步地思考。“on error goto”的更好替代方法是使用包装函数,并在错误时从内部函数`return code;`。将资源分配和清理留给外部包装器。因此在 2 个不同的函数中分离资源分配和算法。更简洁的设计,更容易阅读,没有转到辩论。一般来说,我建议远离“Linux 内核编码风格”文档。 (2认同)