>
defer just changes the problem from forgetting to write free to forgetting to write defer.That's the case only the first time you write code:
{
foo *f = new_foo(); // step 1
/* lots of code */ // step 3
free(f); // step 2
}
vs
{
foo *f = new_foo(); // step 1
defer free(f); // step 2
/* lots of code */ // step 3
}
Sure, in both cases you can forget step 2. But what about
review? With defer, the init and cleanup code are besides each other, and a missing defer would be immediately suspicious.
Without defer, you'd have to check the end of the block to make sure the cleanup code is there. The absence of the cleanup code wouldn't jump to your eyes the same way the absence of defer would. In the long run, this makes defer significantly harder to forget.
---
Another significant advantage of defer is that it can handle several exit points. Imagine this code:
Foo f = new_foo();
defer free(f);
if (!f) {
return FAIL_FOO;
}
Bar b = new_bar();
defer free(b);
if (!b) {
return FAIL_BAR;
}
Baz z = new_baz();
defer free(z);
if (!z) {
return FAIL_BAZ;
}
/* business logic */
/* business logic */
/* business logic */
return SUCCESS;
Now the same, without defer:
Foo f = new_foo();
if (!f) {
free(f);
return FAIL_FOO;
}
Bar b = new_bar();
if (!b) {
free(f);
free(b);
return FAIL_BAR;
}
Baz z = new_baz();
if (!z) {
free(f);
free(b);
free(z);
return FAIL_BAZ;
}
/* business logic */
/* business logic */
/* business logic */
free(f);
free(b);
free(z);
return SUCCESS;
You really don't want to repeat yourself like that, you'd be liable to forget something. Now we
could use `goto` and a return value:
ReturnValue retval = SUCCESS;
Foo f = new_foo();
if (!f) {
retval = FAIL_FOO;
goto cleanup;
}
Bar b = new_bar();
if (!b) {
retval FAIL_BAR;
goto cleanup;
}
Baz z = new_baz();
if (!z) {
retval FAIL_BAZ;
goto cleanup;
}
/* business logic */
/* business logic */
/* business logic */
cleanup:
free(f);
free(b);
free(z);
return retval;
Better, except maybe the fact that Q/A hates you. All is not lost, you can still please them with a single exit point (pattern seen in the real world):
ReturnValue retval = SUCCESS;
Foo f = new_foo();
if (f) {
Bar b = new_bar();
if (b) {
Baz z = new_baz();
if (z) {
/* business logic */
/* business logic */
/* business logic */
} else {
retval FAIL_BAZ;
}
free(z);
} else {
retval FAIL_BAR;
}
free(b);
} else {
retval = FAIL_FOO;
}
free(f);
return retval;
To be honest this may be the worst of them all.
---
The only real contenders for this use case are defer and goto, and even then I think I prefer defer.