Довольно часто приходится захватывать некоторые ресурсы по принципу "все, либо ничего". Например, необходимо выделить память под структурку, а потом еще и для ее членов, далее открыть файл и записать его хэндл в эту структуру и в конце концов вернуть указатель на эту самую структуру. Но если что-то упало, то необходимо освободить все ранее захваченное и вернуть 0.
Пример с выделением памяти:
typedef struct
{
char *url;
char *tag;
char *last_modified;
wchar_t *tmp_name;
} dl_record_t;
Без goto приходится писать нечто вроде:
static dl_record_t *
alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
{
dl_record_t *record = (dl_record_t *)malloc(sizeof(*record));
if (record)
{
record->url = (char *)malloc(url_len + 1);
if (!record->url)
{
free(record);
record = 0;
}
else
{
record->tag = (char *)malloc(tag_len + 1);
if (!record->tag)
{
free(record->url);
free(record);
record = 0;
}
else
{
record->last_modified = (char *)malloc(last_modified_len + 1);
if (!record->last_modified)
{
free(record->url);
free(record->tag);
free(record);
record = 0;
}
else
{
record->tmp_name = (wchar_t *)malloc( (tmp_name_len + 1) * sizeof(wchar_t) );
if (record->file_name == SYN_NULL)
{
free(record->url);
free(record->tag);
free(record->last_modified);
free(record);
record = 0;
}
}
}
}
}
return record;
}
Или так (но peer review это скорее всего не пройдет):
static dl_record_t *
alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
{
int err_code = 0;
dl_record_t *record = (dl_record_t *)malloc(sizeof(*record));
if (record)
{
++err_code; // 1
record->url = (char *)malloc(url_len + 1);
if (record->url)
{
++err_code; // 2
record->tag = (char *)malloc(tag_len + 1);
if (record->tag)
{
++err_code; // 3
record->last_modified = (char *)malloc(last_modified_len + 1);
if (record->last_modified)
{
++err_code; // 4
record->tmp_name = (wchar_t *)malloc( (tmp_name_len + 1) * sizeof(wchar_t) );
if (record->file_name)
{
++err_code; // 5
}
}
}
}
}
switch(err_code)
{
case 4: free(record->last_modified);
case 3: free(record->tag);
case 2: free(record->url);
case 1: free(record);
record = 0;
}
return record;
}
Или еще такой вариант:
static dl_record_t *
alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
{
int status = 0;
dl_record_t *record = (dl_record_t *)malloc(sizeof(*record));
if (record)
{
memset(record, 0, sizeof(*record));
status = (record->url = (char *)malloc(url_len + 1)) != 0;
}
if (status)
{
status = (record->tag = (char *)malloc(tag_len + 1)) != 0;
}
if (status)
{
status = (record->last_modified = (char *)malloc(last_modified_len + 1)) != 0;
}
if (status)
{
status = (record->tmp_name = (wchar_t *)malloc( (tmp_name_len + 1) * sizeof(wchar_t) )) != 0;
}
if (!status && record)
{
if (record->last_modified)
{
free(record->last_modified);
}
if (record->tag)
{
free(record->tag);
}
if (record->url)
{
free(record->url);
}
free(record);
record = 0;
}
return record;
}
Иногда делают и так:
static dl_record_t *
alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
{
int status = 0;
dl_record_t *record;
while (1)
{
record = (dl_record_t *)malloc(sizeof(*record));
if (!record)
{
break;
}
memset(record, 0, sizeof(*record));
record->url = (char *)malloc(url_len + 1);
if (!record->url)
{
break;
}
record->tag = (char *)malloc(tag_len + 1);
if (!record->tag)
{
break;
}
record->last_modified = (char *)malloc(last_modified_len + 1);
if (!record->last_modified)
{
break;
}
record->tmp_name = (wchar_t *)malloc( (tmp_name_len + 1) * sizeof(wchar_t) );
if (!record->tmp_name)
{
break;
}
status = 1;
break;
}
if (!status && record)
{
if (record->last_modified)
{
free(record->last_modified);
}
if (record->tag)
{
free(record->tag);
}
if (record->url)
{
free(record->url);
}
free(record);
record = 0;
}
return record;
}
А вот так выглядит вариант с goto:
static dl_record_t *
alloc_record(int url_len, int tag_len, int last_modified_len, int tmp_name_len)
{
dl_record_t *record = (dl_record_t *)malloc(sizeof(*record));
if (!record)
{
return 0;
}
if (!(record->url = (char *)malloc(url_len + 1)))
{
goto fail_record_url;
}
if (!(record->tag = (char *)malloc(tag_len + 1)))
{
goto fail_tag;
}
if (!(record->last_modified = (char *)malloc(last_modified_len + 1)))
{
goto fail_last_modified;
}
if (!(record->tmp_name = (wchar_t *)malloc( (tmp_name_len + 1) * sizeof(wchar_t) )))
{
goto fail_tmp_name;
}
return record;
fail_tmp_name: free(record->last_modified);
fail_last_modified: free(record->tag);
fail_tag: free(record->url);
fail_record_url: free(record);
return 0;
}
На мой взгляд, вариант с goto более естественен и с точки зрения логики, и с точки зрения производительности. Это касается языка C. В C++ ситуация получше: статусная переменная, хелпер классы (конструктор — захват ресурса, деструктор — освобождение в случае плохого значения статусной переменной) и исключения.
Но все дело в том, что лично мне писать приходится на C.

Какие еще могут быть варианты проведения отката?
25.01.06 07:41: Перенесено модератором из 'Философия программирования' — VladD2