[C][Trick] Resource acquisition
От: andrey.desman  
Дата: 06.04.08 15:05
Оценка: 12 (1)
Навеяно этим топиком: Опять goto :)
Автор: ansi
Дата: 14.01.06


С точки зрения семантики данной проблемы goto конечно рулит. Но во-первых, уж слишком много шума он создает вокруг ключевых операций. А во-вторых, требуется вручную следить за порядком захвата/освобождения, а эти операции могут находиться друг от друга на большом расстоянии, что не дает охватить картину в целом. Недостатки очевидны.

Что требуется, так это такая конструкция, в которой:
— сохраняется семантика метода с goto;
— минимум лишнего (т.е. отсутствие шума);
— метод захвата и освобождения находятся рядом (т.е. фактически указываем пару операций одновременно);

Обернуть в макрос конструкцию с goto стандартными средствами не представляется возможным, так как генерация имен меток для препроцессора задача непосильная. Так что goto все же идет мимо.
Здесь нас спасет простой по своей сути вариант Владека
Автор: Владек
Дата: 14.01.06
, за что ему отдельное спасибо

В итоге имеем такой костыль:
#define USING(alloc, free, action) \
    if (alloc)                     \
    {                              \
        action;                    \
        free;                      \
    }


Таким образом, пример из оригинального топика примет вид:
typedef struct
{
    int dummy;
    // etc.
} base_rec_t;

typedef struct
{
    base_rec_t base;

    char    *url;
    char    *tag;
    char    *last_mod;
    wchar_t *tmp_name;
    
} record_t;

record_t *alloc_record(size_t url_len, size_t tag_len, size_t lm_len, size_t tmp_len)
{
    record_t *r;

    USING(r = malloc(sizeof(*r)), free(r),
    {
        memset(r, 0, sizeof(*r));

        USING(r->url      = malloc(url_len), free(r->url),
        USING(r->tag      = malloc(tag_len), free(r->tag),
        USING(r->last_mod = malloc(lm_len),  free(r->last_mod),
        USING(r->tmp_name = malloc(tmp_len), free(r->tmp_name),
        {
            /* OK */
            return r;
        }
    }
    )))));

    /* OOPS */
    return 0;
}


И до кучи — копирование файла:
int copy_file(char *src_name, char *dst_name)
{
    FILE *src;
    FILE *dst;
    void *buf;
    size_t sz;
    int result = 0;

    const int B_SIZE = 4096;

    USING(src = fopen(src_name, "rb"), fclose(src),
    USING(dst = fopen(dst_name, "wb"), fclose(dst),
    USING(buf = malloc(B_SIZE), free(buf),
    {
        result = 1;

        while (sz = fread(buf, 1, B_SIZE, src))
        {
            if (fwrite(buf, 1, sz, dst) != sz)
            {
                result = 0;
                break;
            }
        }
    }
    )));

    return result;
}


Недостатки:
Помимо того, что эта штука никоим образом не сандартна, так еще и внутри кода (т.е. action) нельзя использовать запятые вне круглых скобок. Нельзя объявить внутри блока несколько переменных сразу, нельзя проинициализировать массив, а оператор ',' придется заключать в скобки. А если попробуете, то компилятор даст по рукам варнингом неверном количестве аргументов макросу и ошибкой о недостающей '}':
d:\projects\dummy\csource.c(75) : warning C4002: too many actual parameters for macro 'USING'
d:\projects\dummy\csource.c(79) : fatal error C1075: end of file found before the left brace '{' at 'd:\projects\dummy\csource.c(52)' was matched


Но может кому-то пригодится
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.