Say I have a function that allocates some struct (object) and has to allocate additional data when setting its fields. Since each allocation can fail, the object creation may only partly succeed. What is the most common way of cleaning this mess up? I suppose the most straight forward thing would be something like this:
struct Obj {
struct ChildObj1 *child_obj1;
struct ChildObj2 *child_obj2;
};
struct Obj *obj_create()
{
struct Obj *obj = malloc(sizeof(struct Obj));
if (!obj) {
return NULL;
}
obj->child_obj1 = child_obj1_create();
if (!obj->child_obj1) {
free(obj);
return NULL;
}
obj->child_obj2 = child_obj2_create();
if (!obj->child_obj2) {
free(obj->child_obj1);
free(obj);
return NULL;
}
return obj;
}
I feel like this could get pretty tedious if there are a lot of objects to be created. Plus you may easily forget to update the "free unwind" when you make changes later on. An alternative I'm pondering is to utilize the "destructor" I'd need anyway, but make sure to zero initialize the struct. Like so:
struct Obj *obj_create()
{
struct Obj *obj = malloc(sizeof(struct Obj));
if (!obj) {
return NULL;
}
memset(obj, 0, sizeof(struct Obj));
obj->child_obj1 = child_obj1_create();
if (!obj->child_obj1) {
obj_destroy(obj);
return NULL;
}
obj->child_obj2 = child_obj2_create();
if (!obj->child_obj2) {
obj_destroy(obj);
return NULL;
}
return obj;
}
void obj_destroy(struct Obj *obj)
{
if (!obj)
return;
if (obj->child_obj2)
free(obj->child_obj2);
if (obj->child_obj1)
free(obj->child_obj1);
free(obj);
}
Are there any problems with this approach? And are there any other better alternatives out there?
This is one of the rare cases where it makes sense to use goto
.
You can use it to jump forward to a set of error cases, with the cleanups happening in the reverse order of the allocations.
struct Obj *obj_create()
{
struct Obj *obj = malloc(sizeof(struct Obj));
if (!obj) {
goto err1;
}
obj->child_obj1 = child_obj1_create();
if (!obj->child_obj1) {
goto err2;
}
obj->child_obj2 = child_obj2_create();
if (!obj->child_obj2) {
goto err3;
}
return obj;
err3:
free(obj->child_obj1);
err2:
free(obj);
err1:
return NULL;
}