Clean ways to do multiple undos in C
This might be one case for using gotos.
Sure, let's try that. Here's a possible implementation:
#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;
}
Yes, it's quite common to use goto in such cases to avoid repeating yourself.
An example:
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;
}
As always you probably also want to keep your functions small and batch together the operations in a meaningful manner to avoid a large "undo-code".
If you have the same signature for your function you can do something like this:
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);
}
You can change the return of one of Do functions to see how it react :)