it-source

C에서 여러 개의 실행 취소를 수행하는 방법을 정리

criticalcode 2023. 8. 19. 10:26
반응형

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...

이것은 gotos를 사용하는 한 가지 경우일 수 있습니다.아니면 여러 개의 내부 기능이...

네, 그런 경우에는 반복하지 않기 위해 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;
}

항상 그렇듯이 큰 "실행 취소 코드"를 피하기 위해 기능을 작게 유지하고 작업을 의미 있는 방식으로 일괄 처리하기를 원할 수도 있습니다.

이것은 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;
}

기능에 동일한 서명이 있는 경우 다음과 같은 작업을 수행할 수 있습니다.

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);
}

Do 함수 중 하나의 반환을 변경하여 반응을 확인할 수 있습니다 :)

완전성을 위해 약간의 난독화:

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;
}

사용하다gotoC에서 정리를 관리합니다.

예를 들어 Linux 커널 코딩 스타일을 확인합니다.

gotosis:

  • 무조건적인 문장은 이해하기 쉽고 내포된 내용을 따르는 것이 줄어듭니다.
  • 수정을 방지할 때 개별 종료 지점을 업데이트하지 않음으로써 발생하는 오류
  • 컴파일러 작업을 저장하여 중복 코드를 최적화합니다 ;)

예:

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;
}

특정한 경우에는 다음과 같이 보일 수 있습니다.

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;
}

가지가 한성공하지 에 함수 할 수 입니다.else if 변수해서 실패한 곳을 추적하는 입니다.switch쉽게 롤백할 수 있는 진술도 있습니다.

int ret=0;
if(Do1()) {
    ret=1;
} else if(Do2()) {
    ret=2;
} else if(Do3()) {
    ret=3;
} else if(Do4()) {
    ret=4;
} else if(Do5()) {
    ret=5;
}

switch(ret) {   
    case 5:  
        Undo4();
    case 4:  
        Undo3();
    case 3:  
        Undo2();
    case 2:  
        Undo1();
    case 1:
        printf("Failed %d\n",ret);
    break; 
}
return ret;

답변에서도 했듯이 예, 다른답설바같와이명한서변에,같이를 사용하면,goto오류 처리의 경우 C에서 적합한 경우가 많습니다.

즉, 가능하면 해당 작업이 수행되지 않았더라도 정리 코드를 안전하게 호출해야 합니다.예를 들어, 다음 대신:

void foo()
{
    int result;
    int* p = malloc(...);
    if (p == NULL) { result = 1; goto err1; }
   
    int* p2 = malloc(...);
    if (p2 == NULL) { result = 2; goto err2; }

    int* p3 = malloc(...);
    if (p3 == NULL) { result = 3; goto err3; }

    // Do something with p, p2, and p3.
    bar(p, p2, p3);

    // Maybe bar() saved references to p and p2, but we don't need
    // p3 anymore.
    free(p3);    

    return 0;

err3:    
    free(p2);
err2:
    free(p);
err1:
    return result;
}

찬성합니다.

void foo()
{
    int result = -1; // Or some generic error code for unknown errors.

    int* p = NULL;
    int* p2 = NULL;
    int* p3 = NULL;

    p = malloc(...);
    if (p == NULL) { result = 1; goto exit; }
   
    p2 = malloc(...);
    if (p2 == NULL) { result = 2; goto exit; }

    p3 = malloc(...);
    if (p3 == NULL) { result = 3; goto exit; }

    // Do something with p, p2, and p3.
    bar(p, p2, p3);

    // Set success *only* on the successful path.
    result = 0;

exit:
    // free(NULL) is a no-op, so this is safe even if p3 was never allocated.
    free(p3);

    if (result != 0)
    {
        free(p2);
        free(p);
    }
    return result;
}

변수를 초기화해야 하기 때문에 효율성이 약간 떨어집니다.NULL하지만 추가 라벨이 필요하지 않기 때문에 더 유지 관리가 가능합니다.코드를 변경할 때 틀린 부분이 적습니다.또한 성공 경로와 실패 경로 모두에 필요한 정리 코드가 있는 경우 코드 중복을 방지할 수 있습니다.

저는 일반적으로 다음과 같은 조건을 내포함으로써 이러한 문제에 접근합니다.

int rval = 1;
if (!Do1()) {
    if (!Do2()) {
        if (!Do3()) {
            if (!Do4()) {
                if (!Do5()) {
                    return 0;
                    // or "goto succeeded", or ...;
                } else {
                    printf("Failed 5");
                    rval = 5;
                }
                Undo4();
            } else {
                printf("Failed 4");
                rval = 4;
            }
            Undo3();
        } else {
            printf("Failed 3");
            rval = 3;
        }
        Undo2();
    } else {
        printf("Failed 2");
        rval = 2;
    }
    Undo1();
} else {
    printf("Failed 1");
    rval = 1;
}
return rval;

보통저는.DoX()수집과 입니다.malloc() 리고그고.UndoX()는 장애가 발생한 경우에만 수행해야 하는 해당 리소스 릴리스입니다.중첩은 해당 수집과 릴리스 간의 연관성을 명확하게 보여주며 실행 취소 작업을 위해 코드를 반복할 필요가 없습니다.또한 매우 쉽게 작성할 수 있습니다. 레이블을 만들거나 유지 관리할 필요가 없으며, 수집을 작성하는 즉시 리소스 릴리스를 올바른 위치에 배치하기가 쉽습니다.

이 접근 방식은 때때로 깊이 중첩된 코드를 생성합니다.그것은 저를 크게 괴롭히지는 않지만, 당신은 그것을 문제로 여길지도 모릅니다.

이 질문은 이미 답변에 과도한 부담을 가지고 있지만, 일부 코드베이스는 기본적으로 예외가 무엇인지 깨끗한 방법으로 처리할 래퍼 코드를 가지고 있다는 것을 지적하고 싶습니다.예를 들어, MuPdf는 예외 처리를 에뮬레이트하는 longjmp를 사용하여 몇 가지 속임수를 구현했습니다.제 생각에는, 그들은 이미 C++을 사용하고 있어야 하지만, 그것은 저 뿐입니다.

그런 포장지는 직접 해보셔도 됩니다.연습으로, 여러분의 요구 사항을 생각해 보고 이를 충족시키기 위해 노력하는 (매우) 조잡한 디자인을 생각해 보겠습니다.

  • 다음 작업이 실패할 경우 취소해야 하는 일련의 작업이 있습니다.
  • 여러 작업은 수행된 순서와 반대로 취소해야 합니다.
  • 실패한 작업은 실행 취소할 수 없습니다.결국 실패했습니다.
  • 도달하지 못한 작업도 원래 실행된 적이 없기 때문에 실행 취소해서는 안 됩니다.
  • 프로그래머가 다음과 같이 명시적으로 말하는 것이 이상적입니다.그는 어떤 작업을 언제 취소해야 하는지 알고 있습니다.

이 문제를 해결하기 위해 몇 가지 매크로를 고안했습니다.

#include <stdio.h>

// Define some variables to keep track of when an error happened, and how many operations should be undone.
// Names are "mangled" by prefixing them with try_. You probably should come up with a better mangling scheme than this.
#define BEGIN_TRY int try_done = 0, try_error = 0, try_count = 0

// Here's how this works:
// - First, count the expression as an operation that may need to be undone;
// - If no error occured yet, do the operation;
// - If it succeeds, count it as a "done" operation;
// - If it fails, signal the error
#define TRY(expression) try_count++; if(!try_error && !(expression)) try_done++; else try_error = 1

// Here we take advantage of the fact that the operations behave like a queue.
// This means that, no matter what, operations need to be undone in the same 
// order everytime, and if an operation needs to be undone when there 
// are N operations, it also needs to be undone when there are N+1 operations.
// So, we don't really need to maintain the queue, if the programmer puts the operations in the correct order already. We just
// need to know how many operations to undo, and how much total operations are there (because we need to start at the end)
#define ON_ERROR(expression) do { if(try_error && try_done >= try_count--) {try_done--; (expression);} } while(0)

// To simplify the test, the "jobs" that we will try to do just pass or fail depending on the argument passed.
int job(int result) {return result;}
void undo(int i) {printf("Undone %d.\n", i);}

#define PASS 0
#define FAIL 1

// Let's test this
int main() {
    BEGIN_TRY;

    // try toying with the order (and quantity) of these.
    // just remember that for each "TRY" there must be one "ON_ERROR"!
    TRY(job(PASS));
    TRY(job(PASS));
    TRY(job(FAIL));
    TRY(job(PASS));

    // Because only the first two operations succeeded, we should only see the effects of undo(2) and undo(1).
    ON_ERROR(undo(4));
    ON_ERROR(undo(3));
    ON_ERROR(undo(2));
    ON_ERROR(undo(1));
}

생방송으로 보세요!

저는 C 전문가가 아니기 때문에 아마 이 문제에 버그가 있을 것입니다(안전한 매크로를 작성하는 것은 어렵습니다). 하지만 제 요점은 다음과 같습니다.당신의 요구 사항을 자세히 생각해 보면, 당신이 해야 할 일은 그것들을 모두 만족시킬 수 있는 해결책을 생각해 내는 것입니다.또 다른 요점은 다음과 같습니다.goto많은 사람들은 매크로를 악으로 봅니다.그들 중 한 명이 되지 마세요: 매크로가 코드를 더 명확하게 하고 읽기 쉽게 한다면, 반드시 그것을 사용하세요.

대부분의 할당 및 초기화 기능과 마찬가지로 함수가 어떤 상태 포인터 또는 핸들을 반환하는 경우, 이를 사용하지 않고도 상당히 깔끔하게 수행할 수 있습니다.goto변수에 초기 값을 부여함으로써.그러면 리소스의 일부만 할당된 경우를 처리할 수 있는 단일 할당 해제 기능을 사용할 수 있습니다.

예:

void *mymemoryblock = NULL;파일 *myfile = NULL;내 주머니에 = -1;
bool allocate_모든 것을 할당합니다.{나의 메모리 블록 = malloc(1000);if (!나의 메모리 블록){거짓으로 반환합니다.}
myfile = fopenfiles/file", "r";if (!내 파일){거짓으로 반환합니다.}
mysocket = socket (AF_INET, SOCK_STREAM, 0);if (내 소켓 < 0){거짓으로 반환합니다.}
true를 반환합니다.}
void delocate_모든 것을 할당 해제합니다.{if (내 소켓 >= 0){닫기(내 소켓);내 소켓 = -1;}
if (내 파일){fclose(내 파일);myfile = NULL;}
if (내 메모리 블록){free(내 메모리 블록);my memory block = NULL;}}

그런 다음 다음에 수행합니다.

if (_모든 것){do_the_day를 수행합니다;}delocate_allocate()

TL;DR:

저는 그것이 다음과 같이 쓰여져야 한다고 믿습니다.

int main (void)
{
  int result = do_func();
  printf("Failed %d\n", result);
}

자세한 설명:

함수 유형에 대해 어떤 것도 가정할 수 없다면 함수 포인터 배열을 쉽게 사용할 수 없습니다. 그렇지 않으면 정답이 될 것입니다.

모든 함수 유형이 호환되지 않는다고 가정하면 호환되지 않는 모든 함수를 포함하는 원래의 모호한 설계를 다른 것으로 포장해야 합니다.

우리는 읽을 수 있고, 유지할 수 있고, 빠른 것을 만들어야 합니다."Do_x"의 실행 취소 동작이 "Do_y"의 실행 취소 동작에 의존하지 않도록 엄격한 결합을 피해야 합니다.

int main (void)
{
  int result = do_func();
  printf("Failed %d\n", result);
}

에▁where디do_func이 함수는 다음과 같습니다.printf는 알고리즘 로직과 분리된 UI 출력입니다.

do_func실제 함수 호출을 중심으로 래퍼 함수처럼 구현되어 결과에 따라 결과를 처리합니다.

포함, (gcc -O3 용시사),do_func 두 기능을 데 ). 이 기능을 사용할 경우에는 오버헤드가 없습니다.

int do_it (void)
{
  if(Do1()) { return 1; };
  if(Do2()) { return 2; };
  if(Do3()) { return 3; };
  if(Do4()) { return 4; };
  if(Do5()) { return 5; };
  return 0;
}

int do_func (void)
{
  int result = do_it();
  if(result != 0)
  {
    undo[result-1]();
  }
  return result;
}

여서기특동어의제다어니됩해에레이작은정▁에 의해 제어됩니다.undo이는 다양한 비호환 함수의 래퍼입니다.호출할 함수, 호출 순서는 각 결과 코드에 연결된 특정 동작의 일부입니다.

우리는 특정 행동을 특정 결과 코드에 연결할 수 있도록 모든 것을 정리해야 합니다.그런 다음 필요할 때 유지보수 중에 동작을 변경해야 하는 경우에만 코드를 한 곳에서 변경합니다.

void Undo_stuff1 (void) { }
void Undo_stuff2 (void) { Undo1(); }
void Undo_stuff3 (void) { Undo2(); Undo1(); }
void Undo_stuff4 (void) { Undo3(); Undo2(); Undo1(); }
void Undo_stuff5 (void) { Undo4(); Undo3(); Undo2(); Undo1(); }

typedef void Undo_stuff_t (void);
static Undo_stuff_t* undo[5] = 
{ 
  Undo_stuff1, 
  Undo_stuff2, 
  Undo_stuff3, 
  Undo_stuff4, 
  Undo_stuff5, 
};

MCVE:

#include <stdbool.h>
#include <stdio.h>

// some nonsense functions:
bool Do1 (void) { puts(__func__); return false; }
bool Do2 (void) { puts(__func__); return false; }
bool Do3 (void) { puts(__func__); return false; }
bool Do4 (void) { puts(__func__); return false; }
bool Do5 (void) { puts(__func__); return true; }
void Undo1 (void) { puts(__func__); }
void Undo2 (void) { puts(__func__); }
void Undo3 (void) { puts(__func__); }
void Undo4 (void) { puts(__func__); }
void Undo5 (void) { puts(__func__); }

// wrappers specifying undo behavior:
void Undo_stuff1 (void) { }
void Undo_stuff2 (void) { Undo1(); }
void Undo_stuff3 (void) { Undo2(); Undo1(); }
void Undo_stuff4 (void) { Undo3(); Undo2(); Undo1(); }
void Undo_stuff5 (void) { Undo4(); Undo3(); Undo2(); Undo1(); }

typedef void Undo_stuff_t (void);
static Undo_stuff_t* undo[5] = 
{ 
  Undo_stuff1, 
  Undo_stuff2, 
  Undo_stuff3, 
  Undo_stuff4, 
  Undo_stuff5, 
};

int do_it (void)
{
  if(Do1()) { return 1; };
  if(Do2()) { return 2; };
  if(Do3()) { return 3; };
  if(Do4()) { return 4; };
  if(Do5()) { return 5; };
  return 0;
}

int do_func (void)
{
  int result = do_it();
  if(result != 0)
  {
    undo[result-1]();
  }
  return result;
}

int main (void)
{
  int result = do_func();
  printf("Failed %d\n", result);
}

출력:

Do1
Do2
Do3
Do4
Do5
Undo4
Undo3
Undo2
Undo1
Failed 5

여기 제가 벌레에 대한 탄력성을 발견한 답이 있습니다.

네. 그것은 사용합니다.goto는 여러분이 에 있는 명확한 .goto구조물이 스파게티 코드를 만들 수 있지만, 이 경우 모든 다른 오류 처리 방법은 보통 이 방법을 사용하는 것보다 스파게티와 더 유사합니다.gotoIMO가 우수합니다.).

어떤 사람들은 이 코드의 형태를 좋아하지 않을 수도 있지만, 저는 이 스타일에 익숙할 때 더 깨끗하고 읽기 쉬우며(물론 모든 것이 정렬되어 있을 때) 오류에 훨씬 더 탄력적이라는 것에 이의를 제기합니다.적절한 린터/정적 분석 설정이 있고 POSIX로 작업 중인 경우 오류 처리를 잘 할 수 있도록 이러한 방식으로 코딩해야 합니다.

static char *readbuf(char *path)
{
    struct stat st;
    char *s = NULL;
    size_t size = 0;
    int fd = -1;

    if (!path) { return NULL; }

    if ((stat(path, &st)) < 0) { perror(path); goto _throw; }

    size = st.st_size;
    if (size == 0) { printf("%s is empty!\n", path); goto _throw; }

    if (!(s = calloc(size, 1))) { perror("calloc"); goto _throw; }

    fd = open(path, O_RDONLY);
    if (fd < -1) { perror(path); goto _throw; }
    if ((read(fd, s, size)) < 0) { perror("read"); goto _throw; }
    close(fd); /* There's really no point checking close for errors */

    return s;

_throw:
    if (fd > 0) close(fd);
    if (s) free(s);
    return NULL;
}
typedef void(*undoer)();
int undo( undoer*const* list ) {
  while(*list) {
    (*list)();
    ++list;
  }
}
void undo_push( undoer** list, undoer* undo ) {
  if (!undo) return;
  // swap
  undoer* tmp = *list;
  *list = undo;
  undo = tmp;
  undo_push( list+1, undo );
}
int func() {
  undoer undoers[6]={0};

  if (Do1()) { printf("Failed 1"); return 1; }
  undo_push( undoers, Undo1 );
  if (Do2()) { undo(undoers); printf("Failed 2"); return 2; }
  undo_push( undoers, Undo2 );
  if (Do3()) { undo(undoers); printf("Failed 3"); return 3; }
  undo_push( undoers, Undo3 );
  if (Do4()) { undo(undoers); printf("Failed 4"); return 4; }
  undo_push( undoers, Undo4 );
  if (Do5()) { undo(undoers); printf("Failed 5"); return 5; }
  return 6;
}

내가 만든undo_pushO(n) 작업을 합니다.이것은 가지고 있는 것보다 덜 효율적입니다.undo언도보다 푸시가 더 많을 것으로 예상되므로 O(n) 작업을 수행합니다.하지만 이 버전은 조금 더 단순했습니다.

좀 더 산업적인 강점을 가진 버전은 머리와 꼬리 포인터와 균등한 용량을 가질 것입니다.

기본 아이디어는 실행 취소 작업의 대기열을 스택에 유지한 다음 정리해야 할 경우 실행하는 것입니다.

이곳의 모든 것은 지역적이기 때문에 우리는 지구의 상태를 오염시키지 않습니다.


struct undoer {
  void(*action)(void*);
  void(*cleanup)(void*);
  void* state;
};

struct undoers {
  undoer* top;
  undoer buff[5];
};
void undo( undoers u ) {
  while (u.top != buff) 
  {
    (u.top->action)(u.top->state);
    if (u.top->cleanup)
      (u.top->cleanup)(u.top->state);
    --u.top;
  }
}
void pundo(void* pu) {
  undo( *(undoers*)pu );
  free(pu);
}
void cleanup_undoers(undoers u) {
  while (u.top != buff) 
  {
    if (u.top->cleanup)
      (u.top->cleanup)(u.top->state);
    --u.top;
  }
}
void pcleanup_undoers(void* pu) {
  cleanup_undoers(*(undoers*)pu);
  free(pu);
}
void push_undoer( undoers* to_here, undoer u ) {
  if (to_here->top != (to_here->buff+5))
  {
    to_here->top = u;
    ++(to_here->top)
    return;
  }
  undoers* chain = (undoers*)malloc( sizeof(undoers) );
  memcpy(chain, to_here, sizeof(undoers));
  memset(to_here, 0, sizeof(undoers));
  undoer chainer;
  chainer.action = pundo;
  chainer.cleanup = pcleanup_undoers;
  chainer.data = chain;
  push_undoer( to_here, chainer );
  push_undoer( to_here, u );
}
void paction( void* p ) {
  (void)(*a)() = ((void)(*)());
  a();
}
void push_undo( undoers* to_here, void(*action)() ) {
  undor u;
  u.action = paction;
  u.cleanup = 0;
  u.data = (void*)action;
  push_undoer(to_here, u);
}

그러면 다음을 얻을 수 있습니다.

int func() {
  undoers u={0};

  if (Do1()) { printf("Failed 1"); return 1; }
  push_undo( &u, Undo1 );
  if (Do2()) { undo(u); printf("Failed 2"); return 2; }
  push_undo( &u, Undo2 );
  if (Do3()) { undo(u); printf("Failed 3"); return 3; }
  push_undo( &u, Undo3 );
  if (Do4()) { undo(u); printf("Failed 4"); return 4; }
  push_undo( &u, Undo4 );
  if (Do5()) { undo(u); printf("Failed 5"); return 5; }
  cleanup_undoers(u);
  return 6;
}

하지만 그것은 점점 더 우스꽝스러워지고 있습니다.

교정기가 없는 것을 시도해 보겠습니다.

int result;
result =                   Do1() ? 1 : 0;
result = result ? result : Do2() ? 2 : 0;
result = result ? result : Do3() ? 3 : 0;
result = result ? result : Do4() ? 4 : 0;
result = result ? result : Do5() ? 5 : 0;

result > 4 ? (Undo5(),0) : 0;
result > 3 ? Undo4() : 0;
result > 2 ? Undo3() : 0;
result > 1 ? Undo2() : 0;
result > 0 ? Undo1() : 0;

result ? printf("Failed %d\r\n", result) : 0;

네.0;는 C C입니다.는 C(및 C++)는 다음과 같습니다.일부 함수가 이 구문과 호환되지 않는 것(예: void)을 반환하는 경우 Undo5() 스타일을 사용할 수 있습니다.

정상적인 접근 방식(고토, 중첩 또는 연결된 경우)은 다음과 같습니다.

int bar(void)
{
  int rc = 0;

  do
  { 
    if (do1())
    {
      rc = 1;
      break;        
    }

    if (do2())
    {
      rc = 2;
      break;        
    }

    ...

    if (do5())
    {
      rc = 5;
      break;        
    }
  } while (0);

  if (rc)
  {
    /* 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;
}

언급URL : https://stackoverflow.com/questions/53444743/clean-ways-to-do-multiple-undos-in-c

반응형